委托类型定义
C#编译器处理委托时先自动产生一个派生自SystemMulticastDelegate的密封类这个类与它的基类SystemDelegate一起为委托提供必要的基础设施以维护以后将要调用的方法列表它含有个编译器生成的方法这个方法的参数与返回值基于委托的声明
public sealed class DelegateName
System
MulticastDelegate
{
public DReturnType Invoke (DParams);
public IAsyncResult BeginInvoke(DParamsAsyncCallback cbobject state);
publicDReturnType EndInvoke(DRefAndOutParamsIAsyncResult result);
}
委托类型实例
某种类型的实例方法和可分配给该类型的目标对象
某种类型的实例方法(包含在形参表中公开的隐藏 this 参数)该委托称为开放式实例委托
静态方法
静态方法和可分配给该方法的第一个参数的目标对象该委托称为通过其第一个参数关闭
何时使用委托而不使用接口
委托和接口都允许类设计器分离类型声明和实现给定的接口可由任何类或结构继承和实现;可以为任何类中的方法创建委托前提是该方法符合委托的方法签名接口引用或委托可由不了解实现该接口或委托方法的类的对象使用既然存在这些相似性那么类设计器何时应使用委托何时又该使用接口呢?
在以下情况中使用委托
当使用事件设计模式时
当封装静态方法可取时
当调用方不需要访问实现该方法的对象中的其他属性方法或接口时
需要方便的组合
当类可能需要该方法的多个实现时
在以下情况中使用接口
当存在一组可能被调用的相关方法时
当类只需要方法的单个实现时
当使用接口的类想要将该接口强制转换为其他接口或类类型时
当正在实现的方法链接到类的类型或标识时例如比较方法
声明委托
声明一个新的委托类型每个委托类型都描述参数的数目和类型以及它可以封装的方法的返回值类型每当需要一组新的参数类型或新的返回值类型时都必须声明一个新的委托类型
public delegate void ProcessBookDelegate(Book book);
实例化委托
声明了委托类型后必须创建委托对象并使之与特定方法关联
在上面的示例中这是通过将 PrintTitle 方法传递给ProcessPaperbackBooks 方法来完成的
bookDBProcessPaperbackBooks(PrintTitle);
这将创建与静态方法 TestPrintTitle 关联的新委托对象类似地对象 totaller 的非静态方法 AddBookToTotal 是按如下方式传递的
bookDBProcessPaperbackBooks(totallerAddBookToTotal);
调用委托
创建委托对象后通常将委托对象传递给将调用该委托的其他代码通过委托对象的名称(后面跟着要传递给委托的参数括在括号内)调用委托对象
processBook(b);
多路广播委托
本示例演示如何组合多路广播委托委托对象的一个用途在于可以使用 + 运算符将它们分配给一个要成为多路广播委托的委托实例组合的委托可调用组成它的那两个委托只有相同类型的委托才可以组合 运算符可用来从组合的委托移除组件委托
根据CIL代码可以发现+=操作符实际上是转换为了一个静态的DelegateCombine()方法的调用=操作符实际上是转换为了一个静态的DelegateRemove()方法的调用
delegate void Del(string s);class TestClass{ static void Hello(string s) { SystemConsoleWriteLine( Hello {}! s); } static void Goodbye(string s) { SystemConsoleWriteLine( Goodbye {}! s); } static void Main() { Del a b c d; a = Hello; b = Goodbye; c = a + b; d = c a; SystemConsoleWriteLine(Invoking delegate a:); a(A); SystemConsoleWriteLine(Invoking delegate b:); b(B); SystemConsoleWriteLine(Invoking delegate c:); c(C); SystemConsoleWriteLine(Invoking delegate d:); d(D); }}
匿名方法
在 之前的 C# 版本中声明委托的唯一方法是使用命名方法C# 引入了匿名方法要将代码块传递为委托参数创建匿名方法则是唯一的方法
如果使用匿名方法则不必创建单独的方法因此减少了实例化委托所需的编码系统开销例如如果创建方法所需的系统开销是不必要的在委托的位置指定代码块就非常有用启动新线程即是一个很好的示例无需为委托创建更多方法线程类即可创建一个线程并且包含该线程执行的代码
buttonClick += delegate(SystemObject o SystemEventArgs e) { SystemWindowsFormsMessageBoxShow(Click!); };
delegate void Del(int x);Del d = delegate(int k) { /* */ };
void StartThread(){ SystemThreadingThread t = new SystemThreadingThread (delegate() { SystemConsoleWrite(Hello ); SystemConsoleWriteLine(World!); }); tStart();}
匿名方法可以访问定义他们的方法的本地变量即匿名方法的外部变量
匿名方法不能访问定义方法中的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 handler = FirstHandler; HandlerMethod handler = SecondHandler; }}
逆变
本示例演示如何将委托与具有某个类型的参数的方法一起使用这些参数是委托签名参数类型的基类型通过逆变以前必须使用若干个不同处理程序的地方现在只要使用一个事件处理程序即可;如现在可以创建一个接收 EventArgs 输入参数的事件处理程序然后可以将该处理程序与发送 MouseEventArgs 类型(作为参数)的 ButtonMouseClick 事件一起使用也可以将该处理程序与发送 KeyEventArgs 参数的 TextBoxKeyDown 事件一起使用
Foo<子类> = Foo<父类>
SystemDateTime lastActivity;public Form(){ InitializeComponent(); lastActivity = new SystemDateTime(); thistextBoxKeyDown += thisMultiHandler; thisbuttonMouseClick += thisMultiHandler; }private void MultiHandler(object sender SystemEventArgs e){ lastActivity = SystemDateTimeNow;}
泛型委托
委托可以定义自己的类型参数引用泛型委托的代码可以指定类型参数以创建已关闭的构造类型就像实例化泛型类或调用泛型方法一样
public delegate void Del<T>(T item);public static void Notify(int i) { }Del<int> m = new Del<int>(Notify);
方法组转换
C#提供了一种叫做方法组转换的简便方法该特性允许我们在调用以委托作为参数的方法时直接提供方法的名称而不用创建委托对象此功能适用于具体委托类型和泛型委托类型并使您可以使用如下简化的语法写入上一行
Del<int> m = 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 : SystemEventArgs { } 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(); sstackEvent += oHandleStackChange;}