.NET 委托
- 委托类型定义
C#编译器处理委托时,先自动产生一个派生自System.MulticastDelegate的密封类。这个类与它的基类System.Delegate一起为委托提供必要的基础设施,以维护以后将要调用的方法列表。它含有3个编译器生成的方法,这3个方法的参数与返回值基于委托的声明。
public sealed class DelegateName :System.MulticastDelegate { public DReturnType Invoke (DParams); public IAsyncResult BeginInvoke(DParams,AsyncCallback cb,object state); public DReturnType EndInvoke(DRefAndOutParams,IAsyncResult result); } |
- 委托类型实例
- 某种类型的实例方法和可分配给该类型的目标对象。
- 某种类型的实例方法(包含在形参表中公开的隐藏 this 参数)。该委托称为开放式实例委托。
- 静态方法。
- 静态方法和可分配给该方法的第一个参数的目标对象。该委托称为通过其第一个参数关闭。
- 何时使用委托而不使用接口
委托和接口都允许类设计器分离类型声明和实现。给定的接口可由任何类或结构继承和实现;可以为任何类中的方法创建委托,前提是该方法符合委托的方法签名。接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢?
- 在以下情况中使用委托
-
- 当使用事件设计模式时。
- 当封装静态方法可取时。
- 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。
- 需要方便的组合。
- 当类可能需要该方法的多个实现时。
- 在以下情况中使用接口
- 当存在一组可能被调用的相关方法时。
- 当类只需要方法的单个实现时。
- 当使用接口的类想要将该接口强制转换为其他接口或类类型时。
- 当正在实现的方法链接到类的类型或标识时:例如比较方法。
- 声明委托
声明一个新的委托类型。每个委托类型都描述参数的数目和类型,以及它可以封装的方法的返回值类型。每当需要一组新的参数类型或新的返回值类型时,都必须声明一个新的委托类型。
public delegate void ProcessBookDelegate(Book book);
|
- 实例化委托
声明了委托类型后,必须创建委托对象并使之与特定方法关联。
在上面的示例中,这是通过将 PrintTitle 方法传递给ProcessPaperbackBooks 方法来完成的。
bookDB.ProcessPaperbackBooks(PrintTitle);
|
这将创建与静态方法 Test.PrintTitle 关联的新委托对象。类似地,对象 totaller 的非静态方法 AddBookToTotal 是按如下方式传递的
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
|
- 调用委托
创建委托对象后,通常将委托对象传递给将调用该委托的其他代码。通过委托对象的名称(后面跟着要传递给委托的参数,括在括号内)调用委托对象。
processBook(b);
|
- 多路广播委托
本示例演示如何组合多路广播委托。委托对象的一个用途在于,可以使用 + 运算符将它们分配给一个要成为多路广播委托的委托实例。组合的委托可调用组成它的那两个委托。只有相同类型的委托才可以组合。- 运算符可用来从组合的委托移除组件委托。
根据CIL代码可以发现,+=操作符实际上是转换为了一个静态的Delegate.Combine()方法的调用。-=操作符实际上是转换为了一个静态的Delegate.Remove()方法的调用。
delegate void Del(string s);
class TestClass
{
static void Hello(string s)
{
System.Console.WriteLine(" Hello, {0}!", s);
}
static void Goodbye(string s)
{
System.Console.WriteLine(" Goodbye, {0}!", s);
}
static void Main()
{
Del a, b, c, d;
a = Hello;
b = Goodbye;
c = a + b;
d = c - a;
System.Console.WriteLine("Invoking delegate a:");
a("A");
System.Console.WriteLine("Invoking delegate b:");
b("B");
System.Console.WriteLine("Invoking delegate c:");
c("C");
System.Console.WriteLine("Invoking delegate d:");
d("D");
}
}
|
- 匿名方法
在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。C# 2.0 引入了匿名方法。要将代码块传递为委托参数,创建匿名方法则是唯一的方法。
如果使用匿名方法,则不必创建单独的方法,因此减少了实例化委托所需的编码系统开销。例如,如果创建方法所需的系统开销是不必要的,在委托的位置指定代码块就非常有用。启动新线程即是一个很好的示例。无需为委托创建更多方法,线程类即可创建一个线程并且包含该线程执行的代码。
button1.Click += delegate(System.Object o, System.EventArgs e)
{ System.Windows.Forms.MessageBox.Show("Click!"); };
delegate void Del(int x);
Del d = delegate(int k) { /* ... */ };
void StartThread()
{
System.Threading.Thread t1 = new System.Threading.Thread
(delegate()
{
System.Console.Write("Hello, ");
System.Console.WriteLine("World!");
});
t1.Start();
}
|
匿名方法可以访问定义他们的方法的本地变量,即匿名方法的外部变量。
- 匿名方法不能访问定义方法中的ref或out参数
- 匿名方法中的本地变量不能与外部方法的本地变量重名
- 匿名方法可以访问外部类作用域中的实例变量或静态变量
- 委托中的协变和逆变
将方法签名与委托类型匹配时,协变和逆变为您提供了一定程度的灵活性。
协变也称为宽松委托,协变允许构造一个委托,指向返回类及其相关继承体系的方法。
逆变允许方法具有的派生参数类型比委托类型中的更少。
-
- 协变
本示例演示如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。由 SecondHandler 返回的数据类型是 Dogs 类型,它是由委托中定义的 Mammals 类型派生的。
Foo<父类> = Foo<子类>
class Mammals{}
class Dogs : Mammals{}
class Program
{
public delegate Mammals HandlerMethod();
public static Mammals FirstHandler(){return null;}
public static Dogs SecondHandler(){return null;}
static void Main()
{
HandlerMethod handler1 = FirstHandler;
HandlerMethod handler2 = SecondHandler;
}
}
|
-
- 逆变
Foo<子类> = Foo<父类>
System.DateTime lastActivity;
public Form1()
{
InitializeComponent();
lastActivity = new System.DateTime();
this.textBox1.KeyDown += this.MultiHandler;
this.button1.MouseClick += this.MultiHandler;
}
private void MultiHandler(object sender, System.EventArgs e)
{
lastActivity = System.DateTime.Now;
}
|
- 泛型委托
委托可以定义自己的类型参数。引用泛型委托的代码可以指定类型参数以创建已关闭的构造类型,就像实例化泛型类或调用泛型方法一样。
public delegate void Del<T>(T item);
public static void Notify(int i) { }
Del<int> m1 = new Del<int>(Notify);
|
- 方法组转换
C#提供了一种叫做方法组转换的简便方法,该特性允许我们在调用以委托作为参数的方法时直接提供方法的名称,而不用创建委托对象,此功能适用于具体委托类型和泛型委托类型,并使您可以使用如下简化的语法写入上一行
Del<int> m2 = Notify;
|
- 泛型类内部定义委托使用泛型类类型参数
在泛型类内部定义的委托使用泛型类类型参数的方式可以与类方法所使用的方式相同。
class Stack<T>
{
T[] items;
int index;
public delegate void StackDelegate(T[] items);
}
|
- 引用委托的代码必须指定包含类的类型变量
private static void DoWork(float[] items) { }
public static void TestStack()
{
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate d = DoWork;
}
|
- 泛型委托取代传统装箱拆箱委托
根据典型设计模式定义事件时,泛型委托尤其有用,因为发送方参数可以为强类型,不再需要强制转换成 Object,或反向强制转换。
delegate void StackEventHandler<T, U>(T sender, U eventArgs);
class Stack<T>
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
protected virtual void OnStackChanged(StackEventArgs a)
{
stackEvent(this, a);
}
}
class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}
public static void Test()
{
Stack<double> s = new Stack<double>();
SampleClass o = new SampleClass();
s.stackEvent += o.HandleStackChange;
}
|