委托与Lambda表达式

委托

委托(delegate)是一种引用类型,也是代码块传递的方式,比如,将一个方法传递到指定的位置。

下面的代码,我们先来看委托的定义。

C#
using System;
/* 委托类型 */
public delegate void DFactory(int x, int y);
/* 测试类 */
public class C9
{
    private static void DefaultFactory(int x,int y)
    {
        Console.WriteLine("x={0}, y={1}", x, y);
    }
    //
    public DFactory Factory = new DFactory(DefaultFactory);
    //
    public void Work(int x,int y)
    {
        Factory(x, y);
    }
    //
}

代码中,首先定义了DFactory委托类型,我们可以看到,除了delegate关键字,它与方法的声明非常相似,包括返回值类型、名称和参数列表。接下来,在C9类中定义了三个成员,分别是:

  • DefaultFactory()静态方法,请注意它的返回类型和参数列表与DFactory委托类型相同。
  • Factory字段,通过new关键字实例化为DFactory委托类型,并指定真正的执行代码就是DefaultFactory()方法。
  • Work()方法,直接通过Factory委托来执行代码,默认情况下,它执行的就是DefaultFactory()方法。

下面的代码,首先来看C9类的默认执行结果。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 99, y = 10;
            C9 c9 = new C9();
            c9.Work(x, y);
        }
    }
}

代码执行结果如下图。

下面的代码,我们在不修改C9类代码的情况下,改变Work()方法执行的代码,方法就是通过修改Factory字段的值来实现。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 99, y = 10;
            C9 c9 = new C9();
            c9.Factory = new DFactory(AddFactory);
            c9.Work(x, y);
        }
        //
        static void AddFactory(int x,int y)
        {
            Console.WriteLine("x+y={0}", x + y);
        }
    }
}

本例,我们定义了AddFactory()方法,它定义为静态方法,并且返回值类型和参数与DFactory委托类型相同。Main()方法中,将c9对象的Factory字段重新实例化并指定调用了AddFactory()方法。代码执行结果如下图。

从这个示例可以看出,只需要使用一个返回值类型、参数和委托类型定义相同的方法,就可以委托对象的具体实现。

实际上,如果委托定义的返回值为void,也就是没有返回值,我们还可以将多个方法同时添加到委托对象,称为多路广播委托。如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 99, y = 10;
            C9 c9 = new C9();
            c9.Factory += new DFactory(AddFactory);
            c9.Factory += new DFactory(MinusFactory);
            c9.Work(x, y);
        }
        //
        static void AddFactory(int x,int y)
        {
            Console.WriteLine("x+y={0}", x + y);
        }
        //
        static void MinusFactory(int x, int y)
        {
            Console.WriteLine("x-y={0}", x + y);
        }
    }
}

本例,我们使用+=运算符将两个DFactory对象累加到c9对象的Factory字段,分别调用了AddFactory()方法和MinusFactory()方法,再加上其默认的方法,Work()方法实际调用了三个方法,代码执行结果如下图。

Lambda表达式

如果委托只需要定义一组代码,可以不单独定义方法,而是使用Lambda表达式简化代码,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 99, y = 10;
            C9 c9 = new C9();
            c9.Factory += (int a,int b) =>
            {
                Console.WriteLine("x*y={0}", a*b);
            };
            c9.Work(x, y);
        }
    }
}

代码执行结果如下图。

下面的代码,单独看一下Lambda表达式部分。

C#
(int a,int b) =>
{
    Console.WriteLine("x*y={0}", a*b);
}

其中:

  • 一对圆括号中定义了参数列表,这与对应的委托类型相同。
  • =>运算符是Lambda表达式专用运算。
  • =>运算符后面的一对花括号中就是Lambda表达式的实现部分。如果委托类型中需要返回值,则可以使用return语句返回。

下面,我们结合集合成员的排序操作再来一些Lambda表达式的具体应用。

List<>中的自定义排序

在List<>对象中,默认情况下可以使用Sort()方法对成员排序,如下面的代码。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> lst = new List<int>();
            lst.AddRange(new int[] { 1, 26, 6, 2, 5, 19 });
            lst.Sort();
            for (int i = 0; i < lst.Count; i++)
                Console.WriteLine(lst[i]);
        }
    }
}

代码执行结果如下图。

如果数值类型,Sort()方法的表现是没有问题的,不过,如果数字是字符串形式,则排序会将字符编码进行排序,如下面的代码。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lst = new List<string>();
            lst.AddRange(new string[] { "1", "26", "6", "2", "5", "19" });
            lst.Sort();
            for (int i = 0; i < lst.Count; i++)
                Console.WriteLine(lst[i]);
        }
    }
}

代码执行结果如下图。

如果需要改变Sort()方法的默认排序规则,可以使用一个重载版本的Sort()方法,其参数是一个Comparison<T>委托类型,其定义如下。

C#
public delegate int Comparison<in T>(T x, T y)

下面的代码,我们就来修改字符串的排序规则。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lst = new List<string>();
            lst.AddRange(new string[] { "1", "26", "6", "2", "5", "19" });
            //
            lst.Sort((string x,string y) => {
                decimal a, b;
                if(decimal.TryParse(x,out a) && decimal.TryParse(y,out b))
                {
                    if (a == b) return 0;
                    else if (a > b) return 1;
                    else return -1;
                }
                else
                {
                    return string.Compare(x, y);
                }
            });
            //
            for (int i = 0; i < lst.Count; i++)
                Console.WriteLine(lst[i]);
        }
    }
}

代码执行结果如下图。

本例,我们通过Lambda表达式实现了一个Comparison<T>委托,它包含两个参数,当参数一等于参数二时返回0,参数一大于参数二时返回1,参数一小于参数二时返回-1,这是比较的一般规则,默认会按升序排列。

如果需要降序排列,只需要改变返回值为1和-1的规则就可以了,如下面的代码。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lst = new List<string>();
            lst.AddRange(new string[] { "1", "26", "6", "2", "5", "19" });
            //
            lst.Sort((string x,string y) => {
                decimal a, b;
                if(decimal.TryParse(x,out a) && decimal.TryParse(y,out b))
                {
                    if (a == b) return 0;
                    else if (a > b) return -1;
                    else return 1;
                }
                else
                {
                    return string.Compare(x, y);
                }
            });
            //
            for (int i = 0; i < lst.Count; i++)
                Console.WriteLine(lst[i]);
        }
    }
}

代码执行结果如下图。

事件

委托的应用之一就是实现组件的事件,如按钮(Button)的单击事件;界面中可能有多个按钮,每个按钮的功能也各有不同,所以,无法在创建按钮组件的时候确定单击后的操作,此时,就可以将按钮的单击操作定义为一个事件(event),并在特定的时机触发。

事件的实现与委托应很相似,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            C10 c10 = new C10() { ID = "e01" };
            c10.EventTest += (object obj, DateTime ts) =>
            {
                Console.WriteLine("ID='{0}' , Time='{1}'",
                     (obj as C10).ID,
                     ts.ToString());
            };
            //
            c10.RaiseEvent();
        }
    }
}

代码执行会显示c10对象的ID属性和程序运行的时间。

开发Windows窗体应用或Web窗体应用时,可以使用大量的控件,其中就包括了很多事件,在编写这些事件的响应代码时,就注意相应的委托类型的定义,以便正确地获取对象及事件相关数据。

本站内容均为原创作品,转载请注明出处,本页面网址为:http://caohuayu.com/chy/article/Article.aspx?code=cc002014