读<<CLR via C#>>总结(10) 详谈委托

  首先弄清楚以下几个问题:

  1,什么是委托?
    委托是一个类(可以通过查看IL代码证明),而且是类型安全的。
    委托对象相当于方法包装器,使方法能通过包装器进行间接回调。

  2,使用委托的好处?
    使用委托可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,提高了程序的可扩展性。

一,如何使用委托

  代码如下:

namespace DelegateDemo1
{
    //1,声明委托
    internal delegate void PrintMyName(string s);

    public class Test
    {
        public static void PrintChineseName(string name)
        {
            Console.WriteLine("My chinese name is:{0}",name);  
        }

        public void PrintEnglishName(string name)
        {
            Console.WriteLine("My english name is:{0}", name);
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();

            //2,创建委托对象
            PrintMyName pmn;
            pmn = new PrintMyName(Test.PrintChineseName);//或用快捷语法:PrintMyName pmn = Test.PrintChineseName

            //3,调用委托
            if (pmn != null)
            {
                pmn("鲁宁");
            }

            pmn = t.PrintEnglishName;//快捷语法
            if (pmn != null)
            {
                pmn("Mcgrady");
            }
            Console.ReadKey();

            /*程序输出结果为:
             My chinese name is:鲁宁
             My english name is:Mcgrady
             */
        }
    }
}

总结:
  1,委托声明使用关键字delegate,与方法相似有返回值和签名,但没有方法体。
  2,实例化委托对象时可以使用new运算符,也可以使用快捷语法,通常快捷语法更常用。
  3,供委托对象包装的方法返回类型和签名必须与委托匹配。
  4,调用委托与调用方法相似,但调用前最好判断委托对象的方法调用列表是否为空。

二,委托揭秘

  先看一下编译器生成的代码,如图:

从这张图中我们可以得到如下信息:
1,委托的确是生成了一个类,同时也对文章开头的观点进行了验证。当编译器看到internal delegate void PrintMyName(string s);这行代码的时候就会自动定义一个完整的类,它包含四个方法,一个构造器,Invoke,BeginInvoke,EndInvoke。代码如下:

internal class PrintMyName : System.MulticastDelegate
    { 
        //构造器
        public PrintMyName(Object obj,IntPtr method);

        //这个方法和源代码指定的原型一样
        public virtual void Invoke(string s);

        public virtual IAsyncResult BeginInvoke(string s,AsyncCallback callback,Object obj);
        public virtual void EndInvoke(IAsyncResult result);
    }

2,所有的委托都派生自System.MulticastDelegate类,而System.MulticastDelegate又派生自System.Delegate类。
  其中MulticastDelegate中定义了三个非公共字段,分别是_target,_methodPtr,_invocationList。Delegate类中定义了两个只读的公共实例属性,分别是Target和Method。Target属性返回保存在MulticastDelegate中字段_target的值;Method属性返回保存在MulticastDelegate中字段_methodPtr的值。下面是实际应用的例子。

public class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();

            PrintMyName pmn;
            pmn = new PrintMyName(Test.PrintChineseName);//或用快捷语法:PrintMyName pmn = Test.PrintChineseName
            Console.WriteLine("Target:{0},Method:{1}",pmn.Target,pmn.Method);

            pmn = t.PrintEnglishName;
            Console.WriteLine("Target:{0},Method:{1}", pmn.Target, pmn.Method);

            Console.ReadKey();
        }
    }

 程序输出结果:

注意:如果委托对象包装的是一个静态方法,Target将返回Null值,如果是实例方法,那么Target的值就是回调方法要操作的对象Test。

三,委托链

  委托链是由委托对象构成的一个集合。利用委托链,可调用集合中的委托所包装的全部方法。对上面的例子加以修改来演示委托链,代码如下:

namespace DelegateDemo1
{
    internal delegate void PrintFunction(string s);

    public class Test
    {
        public static void PrintFunction1(string name)
        {
            Console.WriteLine("This is static method PrintFunction1,create by {0}", name);  
        }

        public void PrintFunction2(string name)
        {
            Console.WriteLine("This is instance method PrintFunction2,create by {0}", name);
        }

        public void PrintFunction3(string name)
        {
            Console.WriteLine("This is instance method PrintFunction3,create by {0}", name);
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();

            PrintFunction pmn1 = new PrintFunction(Test.PrintFunction1);
            PrintFunction pmn2 = t.PrintFunction2;
            PrintFunction pmn3 = t.PrintFunction3;
            PrintFunction pmnChain = null;//定义变量引用委托链

            pmnChain = (PrintFunction)Delegate.Combine(pmnChain, pmn1);//调用Delegate类的静态方法Combine添加委托到委托链
            pmnChain = (PrintFunction)Delegate.Combine(pmnChain, pmn2);
            pmnChain = (PrintFunction)Delegate.Combine(pmnChain, pmn3);
            pmnChain("Mcgrady");//调用委托链
            Console.WriteLine();

            pmnChain = (PrintFunction)Delegate.Remove(pmnChain, pmn2);//调用调用Delegate类的静态方法Remove从委托链中移除委托
            Console.WriteLine("*****The result as below after remove method of PrintFunction2*****");
            pmnChain("Mcgrady");

            Console.ReadKey();
        }
    }
}

程序输出结果:

总结:
  1,构造委托链时,会调用Delegate类的两个静态方法Combine和Remove。C#的编译器自动为委托的实例重载了+=和-=操作符,这两个操作符分别调用Combine和Remove方法,所以我们的代码可以简化,如下面的代码。

public class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();

            PrintFunction pfChain = null;
            pfChain += Test.PrintFunction1;
            pfChain += t.PrintFunction2;
            pfChain += t.PrintFunction3;
            pfChain("Mcgrady");

            Console.WriteLine();
            Console.WriteLine("*****The result as below after remove method of PrintFunction2*****");

            pfChain -= t.PrintFunction2;
            pfChain("Mcgrady");

            Console.ReadKey();
        }
    }

输出结果与使用Delegate的静态方法Combine和Remove一样,这是因为编译器自动用Delegate类的Combine和Remove静态方法分别代替了+=和-=操作符(这一点可以从编译器生成的IL代码中得到验证)。

  2,当构造委托链时,_invocationList字段会被初始化为引用一个委托对象的数组,这一点也非常重要。

原文地址:https://www.cnblogs.com/mcgrady/p/2568780.html