.NET (C#) Internals: Delegates (2)

引言

上篇.NET (C#) Internals: Delegates (1)我们介绍了委托初识、委托本质、委托的实例化方式、协变委托与逆协变委托,本篇将介绍如下主题:

  • 1、委托链直观
  • 2、委托链的本质
    • 2.1、+=操作
    • 2.2、-=操作
  • 3、委托链结构
  • 4、委托链的返回值问题

1、委托链直观


上篇中我们知道调用委托,我们只需要简单地以函数调用的方法,如上篇的例子中cb(“skynet”,23)调用委托cb,实际上调用的是
PersonInfo方法。因为我们将PersonInfo方法与委托cb绑定了,且委托只绑定了一个方法。大家应该见过类似于下面的代码(注意其中
的+=、-=):

using System;
using System.Collections.Generic;
using System.Text;

namespace DelegateTest
{
    class Program
    {
        public delegate void CallBack(string name, int number);
        void PersonInfo(string name, int no)
        {
            System.Console.WriteLine(name);
            System.Console.WriteLine(no);
        }
        void PersonInfo1(string name, int no)
        {
            System.Console.WriteLine("Person's name is: "+name);
            System.Console.WriteLine("Person's number is: "+no);
        }

        static void Main(string[] args)
        {
            Program pr = new Program();
            CallBack cb = pr.PersonInfo;
            cb += pr.PersonInfo1;
            cb("skynet", 23);
            System.Console.WriteLine("-----------------------");
            cb -= pr.PersonInfo;
            cb("skynet", 23);
        }
    }
}

运行上述代码会有如下结果:

  图1、委托链实例

从结果来看,我们知道第一次调用cb(“skynet”,23)时,调用了两个方法PersonInfo、PersonInfo1;第二次调用cb(“skynet”,23)时,只调用了PersonInfo1。从源码我们可以看出是什么造成了这样的输出结果:

第一次调用cb(“skynet”,23)跟我们上篇中第一节委托初识中的代码相比只是多了cb += pr.PersonInfo1
相应的PersonInfo1方法,这是造成委托调用了两个方法的原因所在,+=操作将PersonInfo1方法追加到委托使其绑定两个方法,而且调用
时是按照绑定的顺序调用方法的(试试先绑定PersonInfo1,再+=PersonInfo,然后调用cb(“skynet”,23)就知道了!)。

第二次调用cb(“skynet”,23)是在cb -= pr.PersonInfo,结果委托知道了PersonInfo1方法,说明-=操作移除了PersonInfo方法与委托cb的绑定。

总之,我们可以知道+=、-=操作分别可以向委托追加绑定方法、移除指定方法与委托的绑定。

2、委托链本质

我们在用+=、-=操作时,编译器为我们做了什么呢?让我们用ILDasm查看刚才生成的DelegateTest可执行文件,如下图:

图2、ILDasm查看DelegateTest可执行文件

其中的Main方法的IL代码如下所示:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       108 (0x6c)
  .maxstack  4
  .locals init ([0] class DelegateTest.Program pr,
           [1] class DelegateTest.Program/CallBack cb)
  IL_0000:  nop
  IL_0001:  newobj     instance void DelegateTest.Program::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldftn      instance void DelegateTest.Program::PersonInfo(string,
                                                                      int32)
  IL_000e:  newobj     instance void DelegateTest.Program/CallBack::.ctor(object,
                                                                          native int)
  IL_0013:  stloc.1
  IL_0014:  ldloc.1
  IL_0015:  ldloc.0
  IL_0016:  ldftn      instance void DelegateTest.Program::PersonInfo1(string,
                                                                       int32)
  IL_001c:  newobj     instance void DelegateTest.Program/CallBack::.ctor(object,
                                                                          native int)
  IL_0021:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
                                                                                          class [mscorlib]System.Delegate)
  IL_0026:  castclass  DelegateTest.Program/CallBack
  IL_002b:  stloc.1
  IL_002c:  ldloc.1
  IL_002d:  ldstr      "skynet"
  IL_0032:  ldc.i4.s   23
  IL_0034:  callvirt   instance void DelegateTest.Program/CallBack::Invoke(string,
                                                                           int32)
  IL_0039:  nop
  IL_003a:  ldstr      "-----------------------"
  IL_003f:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0044:  nop
  IL_0045:  ldloc.1
  IL_0046:  ldloc.0
  IL_0047:  ldftn      instance void DelegateTest.Program::PersonInfo(string,
                                                                      int32)
  IL_004d:  newobj     instance void DelegateTest.Program/CallBack::.ctor(object,
                                                                          native int)
  IL_0052:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate,
                                                                                         class [mscorlib]System.Delegate)
  IL_0057:  castclass  DelegateTest.Program/CallBack
  IL_005c:  stloc.1
  IL_005d:  ldloc.1
  IL_005e:  ldstr      "skynet"
  IL_0063:  ldc.i4.s   23
  IL_0065:  callvirt   instance void DelegateTest.Program/CallBack::Invoke(string,
                                                                           int32)
  IL_006a:  nop
  IL_006b:  ret
} // end of method Program::Main

2.1、+=操作

Main方法的IL代码IL_0021:  call     class [mscorlib]System.Delegate
[mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, 
class [mscorlib]System.Delegate) 对应的就是+=操作的IL代码。+=实际上是调用了System.Delegate类的Combine(Delegate,Delegate)方法,参数为两个委托对象。
所以能调用System.Delegate类的方法,是因为System.MulticastDelegate类继承自它,而我们所有的委托都继承自
System.MulticastDelegate类。事实上,如果你查MSDN你会发现System.Delegate类的Combine是重载的,共
有两个重载,如下所示:

  1. Delegate.Combine (Delegate[])
    将委托数组的调用列表连接在一起。

    如果 delegates 数组包含为空引用(在 Visual Basic 中为 Nothing) 的项,则将忽略这些项。调用列表中可包含重复项目,即引用同一对象上同一种方法的项目。
  2. Delegate.Combine (Delegate, Delegate)
    将两个委托的调用列表连接在一起。由 .NET Compact Framework 支持。 调用列表中可包含重复项目,即引用同一对象上同一种方法的项目。注意:第一个参数的委托会被先调,第二个参数的委托会被后调用。

2.2、-=操作

Main方法的IL代码中,-=对应IL_0052:  call         class
[mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class
[mscorlib]System.Delegate,class [mscorlib]System.Delegate)。即-=操作实际上是调用了System.Delegate类的Remove(Delegate source,Delegate value)方法。source
——将从中移除 value 的调用列表,value——它提供将从其中移除 source
的调用列表的调用列表。该方法的返回值是一个新委托,其调用列表的构成方法为:获取 source 的调用列表,如果在 source
的调用列表中找到了 value 的调用列表,则从中移除 value 的最后一个调用列表。如果 value 为 空引用(在 Visual
Basic 中为 Nothing),或在 source 的调用列表中没有找到 value 的调用列表,则返回 source。如果 value
的调用列表等于 source 的调用列表,或 source 为空引用,则返回空引用。注意:如果 value 的调用列表在 source 的调用列表中多次出现,则移除最后一个匹配项。就是移除后绑定的调用列表,运行下面代码就可以得出比较清晰的理解:

using System;
using System.Collections.Generic;
using System.Text;

namespace DelegateTest
{
    class Program
    {
        public delegate void CallBack(string name, int number);
        void PersonInfo(string name, int no)
        {
            System.Console.WriteLine(name);
            System.Console.WriteLine(no);
        }
        void PersonInfo1(string name, int no)
        {
            System.Console.WriteLine("Person1's name is: "+name);
            System.Console.WriteLine("Person1's number is: "+no);
        }
        void PersonInfo2(string name, int no)
        {
            System.Console.WriteLine("Person2's name is: " + name);
            System.Console.WriteLine("Person2's number is: " + no);
        }
        static void Main(string[] args)
        {
            Program pr = new Program();
            CallBack cb = pr.PersonInfo;
            cb += pr.PersonInfo1;
            cb += pr.PersonInfo2;
            cb += pr.PersonInfo;
            cb("skynet", 23);
            System.Console.WriteLine("-----------------------");
            cb -= pr.PersonInfo;
            cb("skynet", 23);
        }
    }
}

 

如果你想移除匹配的所有调用列表的话,请使用RemoveAll方法。

public static Delegate RemoveAll ( Delegate source, Delegate value ):如果 value 的调用列表与 source 的调用列表中一组相邻的元素相匹配,则认为 source 的调用列表内存在 value 的调用列表。如果 value 的调用列表在 source 的调用列表中多次出现,则移除所有调用列表。

3、委托链结构

上篇中的委托本质中知道,任何委托都继承自[mscorlib]System.MutlicastDelegate,且其中有一个类型为System.MutlicastDelegate的私有字段_prev,它指向另一个委托对象,通常为空。没错就是这个字段使我们能将多个委托组成一个链成为了可能,此时除了链头_prev都不为空!_prev字段指向前一个委托对象,所以上面例子中的PersonInfo和PersonInfo1组成的委托链可表示为:

图3、委托链

如果再+=绑定一个PersonInfo2方法,将在PersonInfo2的_prev字段指向PersonInfo1,链上的其他内容不变。那我们再来分析一下当绑定多个方法时,Invoke方法到底是怎么执行的呢?它内部代码大概逻辑如下:

public void Invoke(string,int)
{
  if (_prve != null) _prve.Invoke(string ,int);  //如果当前节点的_prev字段不空,就递归调用_prev指向的节点的Invoke方法,直至调用最先绑定的方法。

  _target.method(string ,int);  //最后调用最后绑定的方法
}

其实完全可以把我们在数据结构中学的链表知识运用在此来理解委托,或者说委托链就是一个链表。

4、委托链的返回值问题

上面的委托链例子是委托的返回类型为void的情况,想象一下如果委托的返回类型不空,它是会返回每个委托调用的返回值,还是?让我们看看下面这段代码:

using System;
using System.Collections.Generic;
using System.Text;

namespace DelegateTest
{
    class Program
    {
        public delegate string CallBack(string name, int number);

        string PersonInfo1(string name, int no)
        {
            System.Console.WriteLine("调用了PersonInfo1");
            return "Person1's name is " + name + ",number is " + no;
        }
        string PersonInfo2(string name, int no)
        {
            System.Console.WriteLine("调用了PersonInfo2");
            return "Person2's name is " + name + ",number is " + no;
        }
        static void Main(string[] args)
        {
            Program pr = new Program();
            CallBack cb = pr.PersonInfo1;
            cb += pr.PersonInfo2;            

            System.Console.WriteLine("最终返回结果: "+cb("skynet",23));
        }
    }
}

我们定义的委托的返回类型为string类型,委托实例cb与PersonInfo1和PersonInfo2绑定,运行结果如下图所示:

图4、委托链的返回值

从结果来看我们知道:调用cb("skynet",23)时,PersonInfo1和PersonInfo2都被调用了,但只返回了PersonInfo2的返回值。 可见调用委托链时,只返回最后一个的委托调用!如果我们想返回每个委托调用的返回值,我们该怎么做呢?

设想一下如下情况:我们调用cb("skynet",23)时,它只会返回PersonInfo2的返回值,那我们可不可以获取委托链中绑定的每个方法,然后我们设计自己的调用规则,每次调用绑定方法时将其返回值保持在一个数组里再返回呢?

System.MutlicastDelegate中有一个public sealed override Delegate[] GetInvocationList()方法,它就是返回委托链的调用列表的,它的代码如下:

public sealed override Delegate[] GetInvocationList()
{
    object[] objArray = this._invocationList as object[];
    if (objArray == null)
    {
        return new Delegate[] { this };
    }
    int num = (int) this._invocationCount;
    Delegate[] delegateArray = new Delegate[num];
    for (int i = 0; i < num; i++)
    {
        delegateArray[i] = (Delegate) objArray[i];
    }
    return delegateArray;
}

安装上面的思路我们写出如下代码:

using System;
using System.Collections.Generic;
using System.Text;

namespace DelegateTest
{
    class Program
    {
        public delegate string CallBack(string name, int number);

        string PersonInfo1(string name, int no)
        {
            System.Console.WriteLine("调用了PersonInfo1");
            return "Person1's name is " + name + ",number is " + no;
        }
        string PersonInfo2(string name, int no)
        {
            System.Console.WriteLine("调用了PersonInfo2");
            return "Person2's name is " + name + ",number is " + no;
        }
        static void Main(string[] args)
        {
            Program pr = new Program();
            CallBack cb = pr.PersonInfo1;
            cb += pr.PersonInfo2; 

            StringBuilder strs = new StringBuilder();
            Delegate[] arrayDelegates = cb.GetInvocationList();
            foreach (CallBack d in arrayDelegates)
            {
                strs.AppendFormat("{0}{1}",d("skynet",23),Environment.NewLine);
            }
            System.Console.WriteLine("最终返回结果:");
            System.Console.WriteLine(strs.ToString());
        }
    }
}

运行结果如下图所示:

图5、返回每个委托调用的返回值

从运行结果可以看出,我们思路是可行的,的确达到了返回每个委托调用的返回值的目的。

时间: 2024-08-30 18:25:57

.NET (C#) Internals: Delegates (2)的相关文章

.NET (C#) Internals: Delegates (1)

引言 委托(delegate),这个概念大家应该都知道或许还有一些新人不知道,比如说我就是现在才对delegate有个比较清晰的认识,这里我将深入解析delegate跟大家分享我所知道的,希望能对部分人有所帮助,给大家带来不一样的视角. 本文涉及主题如下: 1.委托初识 2.委托本质 2.1.委托类 2.2.委托构造器 2.3.委托调用 3.实例化委托的几种方式 3.1.使用new操作符实例化委托 3.2.用方法名实例化委托 3.3.用匿名方法实例化委托 3.4.Lambda表达式实例化委托 4

Community Server专题六:Delegates &amp; Events

server 对于CS的分析你可以能会从页面开始,其实那并不是一个很好的方法,因为CS采用了MasterPage和内建的Theme与Skins,页面一层嵌套一层,如果你对CS页面执行机制不了解,或者你是初学者,这个时候可能就会碰壁,接着就放弃了对CS更深入的了解.我希望我的专题能从CS的运行过程开始一步一步地讲解,同时把ASP.NET的运行机理也表述出来,因此学习了解CS的过程就是对ASP.NET深入了解得过程.当然,我个人的开发经验与水平也是有限的,如果在专题中表述有问题,或者有疑问可以直接在

.NET Delegates: A C# Bedtime Story中文版(下篇)转

中文 作者:Chris Sells 译者:荣耀 [译注:C#进阶文章.Chris Sells是<ATL Internals>一书作者之一.译文中所有程 序调试环境均为Microsoft Visual Studio.NET 7.0 Beta2和 Microsoft .NET Framewo rk SDK Beta2.代码就是文章,请仔细阅读代码J] 取得所有结果      现在,peter终于松了一口气.他已经设法满足了所有的监听者,而且不会和特定 实现紧密耦合.然而,他又注意到尽管boss和u

.NET Delegates: A C# Bedtime Story中文版(上篇)转

中文 作者:Chris Sells 译者:荣耀 [译注:C#进阶文章.Chris Sells是<ATL Internals>一书作者之一.译文中所有程 序调试环境均为Microsoft Visual Studio.NET 7.0 Beta2和 Microsoft .NET Framewo rk SDK Beta2.代码就是文章,请仔细阅读代码J] 类型耦合 从前,在南方的一个异国他乡,有一个叫peter的勤劳的工人.他对boss百依百顺,但他 的boss却是个卑鄙无信的小人,他坚持要求pete

Using Delegates and Events 1 (来自一本49美元/817页2002年的书《C#.net web developers guide》)

guid|web Using Delegates and EventsIf you are familiar with Windows programming, you've most likely dealt withcallbacks. Callbacks are method calls that are executed when some event happenshttp://www.syngress.com.70 Chapter 2 • Introducing C# Program

Using Delegates and Events 2 (来自一本49美元/817页2002年的书《C#.net web developers guide》)

guid|web Figure 2.8 ContinuedContinued.78 Chapter 2 • Introducing C# Programming// appears in the message queue. Notice the signature matches// that requried by AddEventCallbackpublic void logAddRequest( string FirstName, string LastName,string Middl

使用Internals Viewer插件查看SQL SERVER内部数据页面

感觉internals viewer这个名字起得很好,内部查看. 这个小工具是我之前看园子里的某大侠转载的文章是看到的 文章地址: SQL Server2008存储结构之堆表.行溢出 http://www.cnblogs.com/trams/archive/2010/09/11/1823727.html 这几天研究了一下这个小工具,发现挺好用的,对入想深入研究或者刚刚学SQLSERVER但是想对SQL有更深入了解的朋友们特别有用 先给出下载地址,这个是codeplex上的一个项目: http:/

ios6-使用ARC声明delegates

问题描述 使用ARC声明delegates 在fastpdfkit中声明delegates: @interface BookmarkViewController : UIViewController <UITableViewDelegate UITableViewDataSource> {//Delegate to get the current page and tell to show a certain page. It can also be used to// get a list

google maps sdk-ios6中使用delegates方法

问题描述 ios6中使用delegates方法 在iphone应用中用到google地图SDK显示附近餐馆,目前已经做到显示位置.但是有几个问题: 1.如何找到地图的中心坐标? 2.如何恢复到当前位置? 3.判断地图被移动了,就是中心坐标改变了?(比如像MKMapView的regionChanged 一样) 解决方案 这是三个答案:1. GMSMapView* _mapView = ...;CLLocationCoordinate2D centre = _mapView.camera.targe