深入理解C#委托委托(delegate)

这是我参与 11 月更文挑战的第 24 天,活动详情查看:2021 最后一次更文挑战

委托(delegate)

委托在某种程度上提供了间接的方法。换言之,不需要直接指定一个要执行的行为,而是将这个行为用某种方式“包含”在一个对象中。

这个对象可以像其他任何对象那样使用。在该对象中,可以执行封装的操作。

可以选择将委托类型看做只定义了一个方法的接口,将委托的实例看做实现了那个接口的一个对象。

委托必须满足4个条件:

  • 声明委托类型;

  • 必须有一个方法包含了要执行的代码;

  • 必须创建一个委托实例;

  • 必须调用(invoke)委托实例。

委托类型和委托实例

委托类型实际上只是参数类型的一个列表以及一个返回类型。它规定了类型的实例能表示的操作。
“委托实例被调用”中的“调用”对应的是invoke,理解为“唤出”更恰当。它和后面的“为这个对象调用方法”中的“调用”稍有不同,后者对应的是call。在英语的语境中,invoke和call的区别在于,在执行一个所有信息都已知的方法时,用call比较恰当。这些信息包括要引用的类型、方法的签名以及方法名。但是,在需要先“唤出”某个东西来帮你调用一个信息不明的方法时,用invoke就比较恰当。

// 1声明委托类型
delegate void StringProcessor(string input);

// 2为委托实例的操作找到一个恰当的方法  
// 一个有正确签名的方法 
void PrintString(string x)
void PrintString(object x)  // 针对兼容

// 3创建委托实例  委托实例的操作
// 指定在调用委托实例时就执行该方法。

StringProcessor proc1, proc2;
// 静态方法
proc1 = new StringProcessor(StaticMethods.PrintString);

// 创建的实例称为操作的目标
InstanceMethods instance = new InstanceMethods();  
// 创建委托实例 
// 调用委托实例时,就会为这个对象调用方法。
proc2 = new StringProcessor(instance.PrintString);  

// 4调用委托实
// 调用一个委托实例的方法,这个方法本身被称为Invoke。
// 在委托类型中,这个方法以委托类型的形式出现,并且具有委托类型声明中指定的相同参数列表和返回类型。
// void Invoke(string input)
proc1("hello");
复制代码

委托实例的调用

proc1("hello"); 
// 编译  
proc1.Invoke("hello");
// 执行调用
PrintString("hello");
复制代码

委托的实质是间接完成某种操作, 给出一些指令,将职责委托给别人。这增大了复杂性,但同时也增加了灵活性。

合并(多播委托)和删除委托

委托实例实际有一个操作列表与之关联,这称为委托实例的调用列表(invocation list)。

System.Delegate类型的静态方法Combine和Remove负责创建新的委托实例。

  • Combine负责将两个委托实例的调用列表连接到一起,
  • Remove负责从一个委托实例中删除另一个实例的调用列表。

委托是不易变的创建了委托实例后,有关它的一切就不能改变。

这样一来,就可以安全地传递委托实例的引用,并把它们与其他委托实例合并,同时不必担心一致性、线程安全性或者是否有其他人试图更改它。

委托实例和string是一样的。因为Delegate.Combine和String.Concat很像——都是合并现有的实例来形成一个新实例,同时根本不更改原始对象。

对于委托实例,原始调用列表被连接到一起。
如果试图将null和委托实例合并到一起,null将被视为带有空调用列表的一个委托。
很少在C#代码中看到对Delegate.Combine的显式调用,一般都是使用+和+=操作符。