a type-safe function pointer

解决方案 »

  1.   

    代表(delegate)使得这样的方案变为可能:其他语言-C++, Pascal, Modula 等可以用功能指针来定位。与C++的功能指针不同,代表完全是面向对象的;与C++指向成员功能不同,代表把一个对象实例和方法都进行封装。
    一个代表声明定义了一个从类System.Delegate 延伸的类。一个代表实例封装一个方法,可调用实体。对于实例方法,一个可调用实体由一个实例和一个实例中的方法组成。对于静态方法,一个可调用实体完全只是由一个方法组成。如果你有一个代表实例和一个适当的参数集合,你就可以用参数来调用这个代表。
    代表的一个有趣而又有用的特性是它不知道或不关心它引用的对象的类。只要方法的签名与代表的签名一致,任何对象都可以作。这使得代表适合作“匿名“调用”。
      

  2.   

    delegate:委托
    委托是 .NET 框架内的常用类,用于生成事件处理机制。委托大体上相当于常用于 C++ 和其他面向对象的语言中的函数指针。但与函数指针不同的是,委托是面向对象的、类型安全的和保险的。另外,函数指针只包含对特定函数的引用,而委托由对对象的引用以及对该对象内一个或多个方法的引用组成。此事件模型使用“委托”将事件绑定到用来处理它们的方法。委托允许其他类通过指定处理程序方法注册事件通知。当发生事件时,委托调用绑定的方法。委托可绑定到单个方法或多个方法,后者又称为多路广播。当创建事件的委托时,您(或“Windows 窗体设计器”)通常创建多路广播事件。极少的例外情况是,某个事件会导致特定过程(如显示对话框),而该过程在逻辑上不在每个事件中重复多次。多路广播委托维护它所绑定到的方法的调用列表。多路广播委托支持将方法添加到调用列表的Combine方法以及移除它的 Remove 方法。 当应用程序记录某个事件时,控件通过调用该事件的委托引发事件。委托接着调用绑定的方法。最常见的情况(多路广播委托)是,委托依次调用调用列表中的每个绑定方法,这样可提供一对多通知。此策略意味着控件不需要维护事件通知的目标对象的列表,即委托处理所有注册和通知。 委托也允许将多个事件绑定到同一个方法,从而允许多对一通知。例如,单击按钮事件和单击菜单命令事件可调用同一委托,然后该委托调用单个方法以相同方式处理各个事件。委托使用的绑定机制是动态的,即委托可在运行时绑定到其签名与事件处理程序的签名匹配的任何方法。此功能使您得以根据条件设置或更改绑定的方法,并动态地将事件处理程序附加到控件上。////////
      

  3.   

    感谢您使用微软产品。Delegate介绍        回调函数自从创建以来就是最有用的一种编程机制。C语言运行时的快速排序函数就调用了一个回调函数,用排序一个数组中的元素。在Windows中,窗体处理函数、钩子函数、异步过程调用以及更多的地方,都需要回调函数。在Microsoft® .NET框架中,回调方法还会有其他的用法。你可以注册回调方法用来得到装载/卸载的通知、未处理异常的通知、数据库/窗体状态改变的通知、文件系统改变的通知、菜单项选择、完成异步操作的通知,还可以做过滤一些元素等事情。        在C/C++中,函数的地址只是一个内存地址。这个地址不携带任何附带信息,譬如说函数的参数数目,参数类型,返回值类型和调用规范。简而言之,C/C++的回调函数不是类型安全的。        在Microsoft® .NET框架中,回调函数就像在不受管理的Windows®编程中一样有用且普遍。但是,Microsoft® .NET框架提供了一种带有类型安全机制的Delegate。我将通过使用一个Delegate来展开我的讨论。在示例1中的代码演示了怎样声明、创建和使用一个Delegate。示例1 DelegateDemo.cs 
    using System;
    using System.WinForms; // In beta2: System.Windows.Forms
    using System.IO;class Set {
       private Object[] items;   public Set(Int32 numItems) {
          items = new Object[numItems];
          for (Int32 i = 0; i < numItems; i++)
             items[i] = i;
       }   // Define a Feedback type 
       // (NOTE: this type is nested within the Set class)
       public delegate void Feedback(
          Object value, Int32 item, Int32 numItems);   public void ProcessItems(Feedback feedback) {
          for (Int32 item = 0; item < items.Length; item++) {
             if (feedback != null) {
                // If any callbacks are specified, call them
                feedback(items[item], item + 1, items.Length);
             }
          }
       }
    }
    class App {
       [STAThreadAttribute]
       static void Main() {
          StaticCallbacks();
          InstanceCallbacks();
       }
       static void StaticCallbacks() {
          // Create a set with 5 items in it
          Set setOfItems = new Set(5);      // Process the items, give no feedback
          setOfItems.ProcessItems(null);
          Console.WriteLine();      // Process the items, give feedback to console
          setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToConsole));
          Console.WriteLine();      // Process the items, give feedback to a message box
          setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToMsgBox));
          Console.WriteLine();      // Process the items, give feedback to the 
          // console AND a message box
          Set.Feedback fb = null;
          fb += new Set.Feedback(App.FeedbackToConsole);
          fb += new Set.Feedback(App.FeedbackToMsgBox);
          setOfItems.ProcessItems(fb);
          Console.WriteLine();
       }   static void FeedbackToConsole(
          Object value, Int32 item, Int32 numItems) {
          Console.WriteLine("Processing item {0} of {1}: {2}.", 
             item, numItems, value);
       }   static void FeedbackToMsgBox(
          Object value, Int32 item, Int32 numItems) {
          MessageBox.Show(String.Format("Processing item {0} of {1}: {2}.",
            item, numItems, value));
       }
       static void InstanceCallbacks() {
          // Create a set with 5 items in it
          Set setOfItems = new Set(5);      // Process the items, give feedback to a file
          App appobj = new App();
          setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));
          Console.WriteLine();
       }   void FeedbackToFile(
          Object value, Int32 item, Int32 numItems) {      StreamWriter sw = new StreamWriter("Status", true);
          sw.WriteLine("Processing item {0} of {1}: {2}.", 
             item, numItems, value);
          sw.Close();
       }
    }        注意在示例1顶端的Set类。假设这个类中包含有一组等待逐个处理的元素。当你创建一个Set对象时,你传给构造函数将要处理的对象数目,构造函输将创建一个对象数组,并将每个元素初始化为一个整数。        Set类还定义了一个属性为Public的Delegate。Delegate标明了一个回调方法的特征。在这个例子中,一个名为Feedback的Delegate标明了一种回调方法的参数为(an Object, an Int32, and another Int32)且返回值为void。在某种程度上讲,Delegate非常象在C/C++中描述函数地址的自定义类型。另外,Set类定义了一个叫做ProcessItems的公有方法。这个方法有一个参数feedback,这是指向一个Feedback Delegate对象的引用。ProcessItems遍历数组中的每一个元素,并调用feedbak参数所指定的回调函数来处理他,把该元素的值、序号和数组中的总元素数传送给这个回调函数,回调函数可以按着各种方法处理每个元素。  用Delegate调用静态方法        StaticCallbacks方法演示了多种调用Delagate的方法。首先创建了一个Set对象,告知其包含5个元素。ProcessItems被调用时,传送了一个null值给参数feedback,这是第一个运用Delegate的例子。ProcessItems代表一个将要在Set所管理的每一个元素上作某种动作的函数。因为在这个例子中feedback为null,所以这些元素不会被任何回调函数处理。        第二个例子中,创建了一个新的Feedback Delegate对象。这个对象封装了一个方法,允许这个方法直接通过Delegate对象被回调。标识将要被封装的方法的方法名(在这里就是App.FeedbackToConsole),会被传给Feedback的构造函数,New操作符将返回一个引用并传给ProcessItems。现在ProcessItems执行时将调用App的FeedbackToConsole方法处理Set中的元素。FeedbackToConsole只是简单得向控制台写一个字符串,显示被处理的元素以及它的值。        第三个例子和第二个例子一样。唯一的差别就是Feedback Delegate封装的是App.FeedbackToMsgBox方法,这个方法产生一个字显示被处理的元素以及它的值,然后显示在一个信息窗口中。        第四个和第五个例子演示了怎样把Delegate连接在一起形成一个链。在这个例子中,创建了一个指向Feedback Delegate的引用变量fb,并初始化为null。这个变量指向一个Delegate链表的头,其值为null表示该链表中没有任何节点。然后创建了一个封装了App.FeedbackToConsole的Feedback Delegate对象。C#的 += 操作符用来这个将Feedback Delegate对象扩展到fb所指向的链表中去。现在fb就指向链表的头节点了。        最后,另一个Feedback Delegate对象封装了对 App.FeedbackToMsgBox的调用。然后再用C#的+=操作符将这个对象也扩展到链表中去,同时fb指向新的链表的头节点。现在,当ProcessItems被调用时,收到将是一个Feedback的链表的头节点的引用。在ProcessItems内,调用回调方法的那一行代码实际上将完成调用Delegate链表中每一个Delegate对象所封装的方法。换句话说,对于Set的每一个元素来讲,FeedbackToConsole将被调用,紧接着还要调用FeedbackToMsgBox。我将在下一个专栏中解释Delegate链是怎样工作的。        有一点需要注意的是,在这个例子里所有的方法都是类型安全的。譬如说,当创建一个Feedback Delegate对象时,编译器将确认App.FeedbackToConsole 和App.FeedbackToMsgBox方法完全符合Feedback Delegate所定义的原型。也就是,两个方法都必须有三个参数(Object, Int32,Int32),并且两个方法都有同样的返回值类型void。如果方法原型不匹配,编译器将会产生如下错误:“error CS0123: The signature of method 'App.FeedbackToMsgBox()' does not match this delegate type.”        调用实例方法        迄今为止,我只讨论了怎样用Delegate调用静态方法。但是Delegate也能用来调用某一个对象的实例方法。对于一个实例方法来说,Delegate必须知道该方法所要操作的对象实例。        为了理解回调实例方法是怎样工作的,我们可以看一下示例1中的InstanceCallbacks方法。这段代码和调用静态方法的代码十分相像,但是注意在Set对象创建后,还创建了一个App对象。这个App对象没有任何相关的域或属性,只是为了演示。当新的Feedback Delegate对象创建的时候,传给构造函数的是appobj.FeedbackToFile。这使得Delegate封装了一个对FeedbakToFile的引用,这是个实例方法,而不是静态的。当这个实例方法被调用时,appobj所指向的对象将被操作(作为一个隐藏的参数)。FeedbackToFile方法就像FeedbackToConsole和FeedbackToMsgBox一样,只是他打开一个文件并把字符串写到文件尾部。        揭开Delegate的神秘面纱        从表面上看,Delegate似乎用起来很简单:你用C#的delegate关键字进行定义,然后用new操作符产生一个实例,再进行调用(只是这里用一个指向Delegate的引用代替方法名)。        但是真正发生的事情要比上面例子所演示更复杂一些,编译器和通用语言运行环境为了隐藏更多的复杂性而作了大量的幕后工作。在这一节中我将着重讲解编译器和通用语言运行环境是怎样联合工作实现Delegate的。这部分知识会对你理解Delegate很有帮助,并将教你怎样快速有效的使用Delegate。我也会涉及到Delegate为你的编码所带来的额外特性。        让我们再来看一下这行代码
    public delegate void Feedback(
       Object value, Int32 item, Int32 numItems);
            当编译器看到上面这行代码时,确实定义了一个完整的类,就像示例2代码所写的那样。示例2 Feedback Class 
    public class Feedback : System.MulticastDelegate {
       // Constructor
       public Feedback(Object target, Int32 methodPtr);   // Method with same prototype as specified by the source code
       public void virtual Invoke(
          Object value, Int32 item, Int32 numItems);   // Methods allowing the callback to be called asynchronously
       // I'll discuss these methods in a future article
       public virtual IAsyncResult BeginInvoke(
          Object value, Int32 item, Int32 numItems, 
          AsyncCallback callback, Object object);
       public virtual void EndInvoke(IAsyncResult result);
    }        实际上,你可以用ILDas.exe察看结果模块来验证编译器确实自动生成了这个类。在这个例子中,编译器定义了一个叫做Feedback的类,是从System.MulticastDelegate继承来的,而System.MulticastDelegate是在框架的类库中定义的。记住所有的Delegate类型都是从System.MulticastDelegate继承来的。注意在这个例子里,Feedback类是公共的,因为在源代码中声明的是公共的Delegate。如果在源代码中声明的是私有的或者是保护的,那么编译器生成的Feedback类也是私有的或者是保护的。你应当知道Delegate可以在类中定义(就象在这个例子中,Feedback在Set类中定义),Delegate也可以在全局定义。基本上,一个Delegate可以在任何能定义类的地方定义,因为Delegate也是类。        因为所有的Delegate类型都是从System.MulticastDelegate继承来的,所以他们都继承了System.MulticastDelegate的域、属性和方法。注意所有的Delegate都有一个需要两个参数的构造函数,两个参数一个是指向一个对象的引用,另一个是代表一个回调函数的整数。但是,如果你察看你的代码你会看到你传过去的参数是像App.FeedbackToConsole 或appobj.FeedbackToFile这样的值。你所有的直觉告诉你这不会被编译!但是编译器知道这是创建了一个Delegate,编译器会分析源代码以决定哪个对象和哪个方法将被引用。一个对象的引用将被传给target参数,同时一个特定的标识某个方法的32位整数(从MethodDef或MethodRef元数据环中获得)将被传给methodPtr参数。对于静态方法来说,传给target参数的将是null。在构造函数内部,这两个参数将被存储到相应的私有域。另外构造函数会将_prev域设置为null。这个域是用来建立MulticastDelegte对象链表的。我现在会忽略_prev域,并在下面的.Net专栏详细讨论。        所以,每个Delegate实际上是一个对某个方法及他所要操作的对象的封装。MulticastDelegate类定义了两个只读的公共实例属性:Target和Method。给定一个Delegate对象,你可以查询这两个属性。Target属性会返回一个在回调时将被操作的对象的引用,如果方法是静态的,那么Target会返回null。Method属性会返回一个标识回调方法的System.Reflection.MethodInfo对象。        这里有几种方法可以使用这些信息。一种是察看一个Delegate是否是一个指定类型的实例方法:Boolean DelegateRefersToInstanceMethodOfType(
       MulticastDelegate d, Type type) {   return((d.Target != null) && d.Target.GetType == type);
    }        你也可以写代码察看一个回调方法是否有指定的名字(比如说FeedbackToMsgBox)
    Boolean DelegateRefersToMethodOfName(
       MulticastDelegate d, String methodName) {   return(d.Method.Name == methodName);
    }
            现在你已经知道Delegate对象是怎样被创建的,那么让我们再来谈一谈回调函数究竟是怎样被激活的。为了更好的说明问题,我们再看一遍Set类的ProcessItems代码:
    public void ProcessItems(Feedback feedback) {
       for (Int32 item = 1; item <= items.Length; item++) {
          if (feedback != null) {
             // If any callbacks are specified, call them
             feedback(items[item], item, items.Length);
          }
       }
    }
            注释下面就是激活回调方法的那行代码。经过仔细观察,我似乎确实调用了一个叫做feedback的函数,还传进去了三个参数。但是这里没有一个叫做feedback的函数。再次强调,编译器知道feedback是一个Delegate对象,并且生成了调用Delagate对象的Invoke方法的代码。换句话讲,编译器看到的是这样的:
    feedback(items[item], item, items.Length);
            但是编译器生成如下代码,并认为源代码就是这样的
    feedback.Invoke(items[item], item, items.Length);微软亚洲技术中心 VC技术支持
    本贴子仅供CSDN的用户作为参考信息使用。其内容不具备任何法律保障。您需要考虑到并承担使用此信息可能带来的风险。具体事项可参见使用条款 (http://www.csdn.net/microsoft/terms.shtm)。