委托是一种新的面向对象语言特性,委托的功能是在CLR的支持下实现的,这就意味着它并不受限于特定的编程语言,比如C#使用delegate关键字来定义委托,其他的.NET编程语言可以使用自己的方式来定义委托。

     以委托作为基础,.NET构造了一个技术大厦,事件驱动、异步调用和Lambda表达式都建立于委托之上,还有许多其他的技术与委托有着密切的联系,掌握委托是探索这些技术领域的前提。

一、委托的简单使用

namespace ConsoleApplication1
{
    public delegate int MathOptDelegate(int x, int y);

    class Program
    {
        static void Main(string[] args)
        {
            MathOptDelegate mathOpt = Add;

            int z = mathOpt(3, 2);
        }

        private static int Add(int x, int y)
        {
            return x + y;
        }
    }
}

从上例可以直观的感受到:委托可以看成是一个方法的"容器",将某一具体的方法"装入"后,就可以把它当成方法一样使用。但是不是所有的方法都可以赋值给mathOpt变量,必须满足一定的条件,定义委托类型时对方法的要求被称为方法的"签名"。

二、深入探索委托技术内幕

1.详解委托类型

用ILDASM打开编译后的项目文件,可以查看代码生成的IL指令。

不难发现使用delegate关键字定义一个委托类型时,其实是定义了一个新类MathOptDelegate,此类派生自MulticastDelegate,而MulticastDelegate又派生自Delegate。可以看到有一个构造函数和Invoke方法,继续查看Main方法,如下图所示:

查看IL_0007代码,发现调用了新生成的MathOptDelegate的构造函数,查看IL_0010代码,发现调用了MathOptDelegate的Invoke方法。所以通过委托变量间接调用方法,实际调用的是MathOptDelegate对象的Invoke方法。

通过查看IL代码,可以看到委托的真实面目,对于以下这条委托定义语句:

public delegate int MathOptDelegate(int x, int y);

C#编译器实际上是按照以下这个"代码模板"进行编译的:

public class MathOptDelegate : System.MultiDelegate
{
    public MathOPtDelegate(Object target,Int32 methodPtr);

    public Int32 virtual Invoke (Int32 value1,Int32 value2);

    public virtual IAsyncResult BeginInvoke(Int32 value1,Int32 value2, AsyncCallback callback,Object object);

    public virtual Int32 EndInvoke(IAsyncResult result);

}

注意MathOptDelegate类的构造函数,它接收两个参数target和methodPtr。

target:引用要调用方法的对象,如果调用的是静态方法,则target = null。

methodPtr:是一个方法指针,代表要调用的对象方法

2.委托调用列表

如果委托仅仅是方法调用的另一种方式,那何必多此一举引入"委托"这一特性?直接调用方法不更简单明了?对此问题的回答是:委托变量不仅可以引用一个方法,还可以组合多个方法并批量执行它们。

namespace ConsoleApplication1
{
    public delegate int MathOptDelegate(int x, int y);

    class Program
    {
        static void Main(string[] args)
        {
            MathOptDelegate a = Add;

            MathOptDelegate b = Divide;

            MathOptDelegate c = a + b;

            MathOptDelegate d = c - a;

            Console.ReadKey();
        }

        private static int Add(int x, int y)
        {
            return x + y;
        }

        private static int Divide(int x, int y)
        {
            return x / y;
        }
    }
}

Delegate定义了一个GetInvocationList静态方法用于获取委托调用列表(委托调用列表其实是委托对象内部所包容的一个数组,存放在数组中的元素是Delegate类型的实例,每个实例引用一个静态或实例方法),如果在代码中调用委托变量,将导致委托调用列表中的所有方法顺序执行,如果委托定义的方法有返回值,则多路委托变量的返回值为委托调用列表中最后一个方法的返回值。

但是委托创建之后,它的委托调用列表是不可更改的。使用"+"或者"-"运算符合并或分割两个委托调用列表,得到的其实是一个新的委托调用列表,可以参考字符串是不可更改的情况。

三、使用预定义的委托

.NET为了方便开发人员的使用,预先定义了几种委托,分别是Action、Function以及Predicate,使用起来都比较简单,所以就不再多介绍。

四、匿名方法和Lambda表达式

使用委托有几个步骤:

1)定义委托类型:

2)定义一个或多个符合委托类型要求的方法;

3)定义委托类型的变量;

4)将第2步定义的方法引用"挂接"到第3步定义的变量,以构建一个"委托调用列表";

5)通过委托变量"间接"调用委托调用列表;

显然,上述步骤很麻烦,能否简化?比如赋值给委托变量的方法只在这一个地方用到,其他的地方都不会调用它,为何需要单独定义成一个方法。基于上述简化开发的考虑,C#引入了"匿名方法"和"Lambda表达式"。

1.匿名方法揭秘

namespace ConsoleApplication1
{
    public delegate int MathOptDelegate(int x, int y);

    class Program
    {
        static void Main(string[] args)
        {
            MathOptDelegate a = delegate(int x, int y)
            {
                return x + y;
            };

            Console.WriteLine(a(2, 3));

            Console.ReadKey();
        }

    }
}

从示例中可以看到,"匿名方法"其实是将方法定义与委托变量赋值两个步骤合在一起,从而省掉了单独定义一个方法的麻烦。

使用ildasm查看上述代码生成的文件,会发现C#编译器自动生成了一个静态的方法:

2.奇特的Lambda表达式

上述示例中给委托变量赋值的语句

MathOptDelegate a = delegate(int x, int y){ return x + y; };

如果使用Lambda表达句,可以进一步简化: MathOptDelegate a  = (x,y) => {return x + y};

所以:Lambda表达式其实就是匿名方法的进一步简化,可以用于定义一个匿名函数,交将其传递给一个委托变量。

 

posted on 2015-08-29 15:37  JustYong  阅读(963)  评论(1编辑  收藏  举报