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

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

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、返回每个委托调用的返回值

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

相关文章
|
1月前
|
Java 物联网 C#
C#/.NET/.NET Core学习路线集合,学习不迷路!
C#/.NET/.NET Core学习路线集合,学习不迷路!
|
4天前
|
开发框架 监控 .NET
C#进阶-ASP.NET WebForms调用ASMX的WebService接口
通过本文的介绍,希望您能深入理解并掌握ASP.NET WebForms中调用ASMX WebService接口的方法和技巧,并在实际项目中灵活运用这些技术,提高开发效率和应用性能。
22 5
|
12天前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
59 13
|
1月前
|
Java 物联网 编译器
C#一分钟浅谈:.NET Core 与 .NET 5 区别
本文对比了 .NET Core 和 .NET 5,从历史背景、主要区别、常见问题及易错点等方面进行了详细分析。.NET Core 侧重跨平台支持和高性能,而 .NET 5 在此基础上统一了 .NET 生态系统,增加了更多新特性和优化。开发者可根据具体需求选择合适的版本。
47 7
|
1月前
|
人工智能 开发框架 前端开发
C#/.NET/.NET Core技术前沿周刊 | 第 12 期(2024年11.01-11.10)
C#/.NET/.NET Core技术前沿周刊 | 第 12 期(2024年11.01-11.10)
|
1月前
|
开发框架 网络协议 .NET
C#/.NET/.NET Core优秀项目和框架2024年10月简报
C#/.NET/.NET Core优秀项目和框架2024年10月简报
|
20天前
|
数据库连接 数据库 C#
Windows下C# 通过ADO.NET方式连接南大通用GBase 8s数据库(上)
Windows下C# 通过ADO.NET方式连接南大通用GBase 8s数据库(上)
|
20天前
|
数据库连接 数据库 C#
Windows下C# 通过ADO.NET方式连接南大通用GBase 8s数据库(下)
本文接续前文,深入讲解了在Windows环境下使用C#和ADO.NET操作南大通用GBase 8s数据库的方法。通过Visual Studio 2022创建项目,添加GBase 8s的DLL引用,并提供了详细的C#代码示例,涵盖数据库连接、表的创建与修改、数据的增删查改等操作,旨在帮助开发者提高数据库管理效率。
|
1月前
|
人工智能 开发框架 安全
C#/.NET/.NET Core技术前沿周刊 | 第 13 期(2024年11.11-11.17)
C#/.NET/.NET Core技术前沿周刊 | 第 13 期(2024年11.11-11.17)
|
1月前
|
网络协议 Unix Linux
精选2款C#/.NET开源且功能强大的网络通信框架
精选2款C#/.NET开源且功能强大的网络通信框架