首页 > 学院 > 开发设计 > 正文

[CLR via C#]17. 委托

2019-11-17 03:19:44
字体:
来源:转载
供稿:网友

[CLR via C#]17. 委托

回调函数是一种非常有用的编程机制,它已经存在很多年了。Microsoft .NET Framework通过委托(delegate)来提供一种回调机制。不同于其他平台(比如非托管C++)的回调机制,委托提供了多得多的功能。例如,委托确保回调方法是类型安全的(这是CLR最重要的目标之一)。委托还允许顺序调用多个方法,并支持调用静态方法和实例方法。一、初识委托为了理解委托,先看看如何使用它。委托4个最基本的步骤:1)定义委托类型2)有一个方法包含要执行的代码3)创建一个委托实例化(包含声明委托对象)4)执行调用(invoke)委托实例具体解释如下:1.定义委托类型委托类型就是参数类型的一个列表以及一个返回类型。
delegate void StringPRocessor(string input);

其中的StringProcessor是一个类型。

2.定义签名相同的方法定义的方法要与委托有类型相同的返回值和参数。
private void GetStringLength(object x){}  //C#2.0以后认为一致

  3.创建委托实例

  创建委托实例就是指定在调用委托实例时执行的方法。
StringProcessor proc1,proc2//GetStringLength 实例方法proc1= new StringProcessor(GetStringLength);//GetString 静态方法proc2 += GetString;

  

  4.调用委托  调用委托就是调用一个委托实例方法。  
proc1("Hello World");

  具体的示例代码:

namespace Program {    //定义委托    delegate void StringProcessor(string input);     class Person    {        string name;        public Person(string name)        {            this.name = name;        }         //定义与委托签名相同的"方法"        public void Say(string message)        {            Console.WriteLine(name+" say:" + message);        }    }     class BackGround    {        //定义与委托签名相同的"静态方法"        public static void Note(string note)        {            Console.Write("{0}", note);        }    }     class Program    {        static void Main(string[] args)        {            Person jon = new Person("Jom");            Person tom = new Person("Tom");            //创建委托实例(第一步)            StringProcessor proc1, proc2, proc3;             //创建委托实例:赋值(第二部)            proc1 = new StringProcessor(jon.Say);            proc2 = new StringProcessor(tom.Say);            proc3 = BackGround.Note;             //调用委托            proc1("Hello jon");            proc2("Hello tom");            proc3("note");             Console.ReadKey();        }    }}

  书中的代码示例:
namespace Test{    // 1.声明委托类型    internal delegate void Feedback(Int32 value);     internal class Program    {        private static void Main(string[] args)        {            StaticDelegateDemo();            InstanceDelegateDemo();            ChainDelegateDemo1(new Program());            ChainDelegateDemo2(new Program());         }         private static void StaticDelegateDemo()        {            Console.WriteLine("----- Static Delegate Demo -----");            Counter(1, 3, null);            // 3.创建委托实例            Counter(1, 3, new Feedback(Program.FeedbackToConsole));            Counter(1, 3, new Feedback(FeedbackToMsgBox));            Console.WriteLine();        }         private static void InstanceDelegateDemo()        {            Console.WriteLine("----- Instance Delegate Demo -----");            Program di = new Program();            // 3.创建委托实例            Counter(1, 3, new Feedback(di.FeedbackToFile));             Console.WriteLine();        }         private static void ChainDelegateDemo1(Program di)        {            Console.WriteLine("----- Chain Delegate Demo 1 -----");            // 3.创建委托实例            Feedback fb1 = new Feedback(FeedbackToConsole);            Feedback fb2 = new Feedback(FeedbackToMsgBox);            Feedback fb3 = new Feedback(di.FeedbackToFile);             Feedback fbChain = null;            fbChain = (Feedback)Delegate.Combine(fbChain, fb1);            fbChain = (Feedback)Delegate.Combine(fbChain, fb2);            fbChain = (Feedback)Delegate.Combine(fbChain, fb3);            Counter(1, 2, fbChain);             Console.WriteLine();            fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));            Counter(1, 2, fbChain);        }         private static void ChainDelegateDemo2(Program di)        {            Console.WriteLine("----- Chain Delegate Demo 2 -----");            Feedback fb1 = new Feedback(FeedbackToConsole);            Feedback fb2 = new Feedback(FeedbackToMsgBox);            Feedback fb3 = new Feedback(di.FeedbackToFile);             Feedback fbChain = null;            fbChain += fb1;            fbChain += fb2;            fbChain += fb3;            Counter(1, 2, fbChain);             Console.WriteLine();            fbChain -= new Feedback(FeedbackToMsgBox);            Counter(1, 2, fbChain);        }         private static void Counter(Int32 from, Int32 to, Feedback fb)        {            for (Int32 val = from; val <= to; val++)            {                // 如果指定了任何回调,就可以调用它                if (fb != null)                    // 4.调用委托                    fb(val);            }        }         // 2.声明签名相同的方法        private static void FeedbackToConsole(Int32 value)        {            Console.WriteLine("Item=" + value);        }         // 2.声明签名相同的方法        private static void FeedbackToMsgBox(Int32 value)        {            Console.WriteLine("Item=" + value);        }         // 2.声明签名相同的方法        private void FeedbackToFile(Int32 value)        {            StreamWriter sw = new StreamWriter("Status", true);            sw.WriteLine("Item=" + value);            sw.Close();        }    }}

二、用委托回调静态方法在上面的代码中,我们可以清楚的看到用委托如何回调静态方法。直接将静态方法绑定到委托的实例上,再通过实例进行调用。将一个方法绑定到一个委托时,C#和CLR都允许引用类型的协变性和逆变性。协变性是指方法能返回从委托的返回类型派生的一个类型。逆变性是指方法获取的参数可以是委托的参数类型的基类。例如下面的委托:
deleget Object MyCallback(FileStream s);

完全可以构造该委托类型的一个实例,并和具有一下原型的一个方法绑定:

String SomeMethod(Stream s);

在这里,SomeMethod的返回类型(String)派生自委托的返回类型(Object);这种协变性是允许的。SomeMethod的参数类型(Stream)是委托的参数类型(FileStream)的基类;这种逆变性是运行部的。

注意,协变性和逆变性只能用于引用类型,不能作用于值类型和void。所以下面示例是错误的:
Int32 SomeMethod(Stream s);//这是错误的

值类型和void之所以不支持协变性和逆变性,是因为它们的存储结构是变化的,而引用类型的存储结构始终是一个指针。

三、用委托回调实例方法使用委托回调实例方法,在上面代码中演示已经非常清楚了,就不细说了。四、委托揭秘从表面看,委托似乎很容易使用:用C#的delegate关键字声明,用熟悉的new操作符构造委托实例,用熟悉的方法调用语法来调用回调函数。然而,实际情况远比前面例子演示的复杂的多。编译器和CLR在幕后做了大量工作来隐藏复杂性。本节重点讲解了编译器和CLR如何协同工作来实现委托。首先让我们重写审视下面的代码:
internal delegate void Feedback(Int32 value);

  看到这行代码,编译器实际会像下面这样定义一个完整的类:

internal class Feedback: System.MulticastDelegate {    // 构造器    public Feedback(Object object, IntPtr method);     // 这个方法和源代码指定的原型一样    public virtual void Invoke(Int32 value);     // 以下方法实现了对回调方法的异步回调    public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object object);    public virtual void EndInvoke(IAsyncResult result);}

  编译器定义的类有4个方法:一个构造器、Invoke、BeginInvoke和EndInvoke。本节重点解释构造器和Invoke,BeginInvoke和EndInvoke看留到后面讲解。

  事实上,可用ILDasm.exe查看生成的程序集,验证编译器真的会自动生成这个类,如图17-1所示:  在这个例子中,编译器定义了一个名为Feedback的类,该类派生自FCL定义的System.MulticastDelegate类型(所有委托类型都派生自System.MulticastDelegate类型)。  提示:System.MulticastDelegate类派生自System.Delegate,后则又派生自System.Object。之所以有两个委托类,是有历史原因的。  从图中可知Feedback的可访问性是private,因为委托在源代码中声明为internal类。如果源代码改成使用public可见性,编译器生成的类也会是public类。要注意,委托类即可嵌套在一个类型中定义,也可以在全局范围中定义。简单地说,由于委托是类,所以凡是能够定义类的地方,都能定义委托。  由于所有委托类型都派生自MulticastDelegate,所以它们继承了MulticastDelegate的字段、属性和方法。在这些成员中,有三个非公共字段是最重要的。  
字段类型说明
_targetSystem.Object当委托对象包装一个静态方法时,这个字段为null。当委托对象包装一个实例方法时,这个字段引用的是回调方法要操作的对象。换言之,这个字段指出了要传给实例方法的隐式参数this的值
_methodPtrSystem.IntPtr一个内部的整数值,CLR用它来标识要回调的方法
_invocationListSystem.Object该字段通常为null。构造一个委托链时,它可以引用一个委托数组。
  注意,所有委托都有一个构造器,它要获取两个参数:一个是对象引用,另一个是引用回调方法的一个整数。然而,如果仔细看下签名的源代码,会发现传递的是Program.FeedbackToConsole和di.FeedbackToFile这样的值,这似乎不可能通过编译吧?然而,C#编译器知道要构造的是委托,所以会分析源代码来确定引用的是哪个对象和方法。对象引用被传给构造器的object参数,标识了方法的一个特殊IntPtr值(从MethodDef或MemberRef元数据token获得)被传给构造器的method参数。对于静态方法,会为object参数传递null值。在构造器内部,这两个实参分别保存在_target和_methodPtr私有字段中。除此之外,构造器还将_invocationList字段设为null,对这个字段的讨论推迟到后面。  所以,每个委托对象实际都是一个包装器,其中包装了一个方法和调用该方法时要操作的一个对象。例如,在执行以下两行代码之后:
    Feedback fbStatic = new Feedback(Program.FeedbackToConsole)
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表