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

简介:

2010-04-11 12:32 by 吴秦, 1764 阅读, 0 评论, 收藏, 编辑

引言

上篇.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);
        }
    }
}

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

image 图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可执行文件,如下图: image

图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组成的委托链可表示为:

绘图1图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绑定,运行结果如下图所示:

image图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());
        }
    }
}

运行结果如下图所示:

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

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




本文转自吴秦博客园博客,原文链接:http://www.cnblogs.com/skynet/archive/2010/04/11/1709455.html,如需转载请自行联系原作者

相关文章
|
13天前
|
算法 Java 中间件
C#/.NET/.NET Core优质学习资料,干货收藏!
C#/.NET/.NET Core优质学习资料,干货收藏!
|
13天前
|
人工智能 开发框架 算法
C#/.NET/.NET Core技术前沿周刊 | 第 2 期(2024年8.19-8.25)
C#/.NET/.NET Core技术前沿周刊 | 第 2 期(2024年8.19-8.25)
|
13天前
|
缓存 开发框架 算法
C#/.NET这些实用的编程技巧你都会了吗?
C#/.NET这些实用的编程技巧你都会了吗?
|
17天前
|
SQL 存储 关系型数据库
C#一分钟浅谈:使用 ADO.NET 进行数据库访问
【9月更文挑战第3天】在.NET开发中,与数据库交互至关重要。ADO.NET是Microsoft提供的用于访问关系型数据库的类库,包含连接数据库、执行SQL命令等功能。本文从基础入手,介绍如何使用ADO.NET进行数据库访问,并提供示例代码,同时讨论常见问题及其解决方案,如连接字符串错误、SQL注入风险和资源泄露等,帮助开发者更好地利用ADO.NET提升应用的安全性和稳定性。
48 6
|
13天前
|
传感器 应用服务中间件 Linux
C#/.NET/.NET Core技术前沿周刊 | 第 3 期(2024年8.26-8.31)
C#/.NET/.NET Core技术前沿周刊 | 第 3 期(2024年8.26-8.31)
|
13天前
|
人工智能 算法 C#
C#/.NET/.NET Core技术前沿周刊 | 第 1 期(2024年8.12-8.18)
C#/.NET/.NET Core技术前沿周刊 | 第 1 期(2024年8.12-8.18)
|
13天前
|
JSON 测试技术 C#
C#/.NET/.NET Core优秀项目框架推荐榜单
C#/.NET/.NET Core优秀项目框架推荐榜单
|
13天前
|
开发框架 .NET Java
C#/.NET/.NET Core自学入门指南
C#/.NET/.NET Core自学入门指南
|
20天前
|
开发者 C# Android开发
Xamarin 与 .NET:解锁现代化移动应用开发的超级武器——深入探讨C#与.NET框架如何赋能跨平台应用,实现高效编码与卓越性能
【8月更文挑战第31天】Xamarin 与 .NET 的结合为开发者提供了强大的平台,用于构建现代化移动应用。通过 C# 和 .NET 框架,Xamarin 可以实现一次编写、多平台运行,覆盖 iOS、Android 和 Windows。这种方式不仅节省了开发时间和成本,还保证了应用的一致性和高质量。Xamarin 是一个开源框架,专为跨平台移动应用开发设计,允许使用 C# 语言和 .NET 核心库构建原生应用,并访问各平台特定功能。微软维护的 Xamarin 是 Visual Studio 生态系统的一部分,极大地提高了开发效率。
49 0
|
20天前
|
开发者 Apache 程序员
揭秘Apache Wicket:页面生命周期背后的神秘力量!
【8月更文挑战第31天】李工是一位热爱Web开发的程序员,近日在技术博客上分享了他对Apache Wicket框架的学习心得,特别是页面生命周期的理解。他认为掌握Wicket页面生命周期对于开发富交互式Web应用至关重要。他通过一个简单的计数器应用示例,详细解释了Wicket的组件化设计理念以及页面和组件在生命周期中的变化。
26 0