以一个列子开始了解委托更容易些,下面是委托的使用方式:
internal delegate void Feedback(int32 value)fb;
//使用
public void main()
{
fb = getsomthing;
fb+=getanything;
if(fb != null)
{
fb(321);
}
fb-=getanything;
fb(123);
}
private void getsomthing(int32 value)
{
Console.WriteLine($"this value is {value}")
}
private static void getanything(int32 value)
{
Console.WriteLine($"this value is anything {value}")
}
编译器在遇到 :internal delegate void Feedback (int32 value);语句时会将它编译为一个类,这个类继承 System.MulticastDelegate 基类,而System.MulticastDelegate 又继承自 System.delegate。
这个类也有构造函数,它要求传递两个参数,一个 object 和一个 IntPtr 。把它们保存到内部的字段中(字段是来自基类),以后会通过它们找到回调函数。
这个类其他的方法有,invoke、BeginIvoke,EndIvoke。这个具体是啥样参照下面的代码:
internal class Feedback : System.MulticastDelegate {
// Constructor
public Feedback(Object @object, IntPtr method);
// Method with same prototype as specified by the source code
public virtual void Invoke(Int32 value);
// Methods allowing the callback to be called asynchronously
public virtual IAsyncResult BeginInvoke(Int32 value,
AsyncCallback callback, Object @object);
public virtual void EndInvoke(IAsyncResult result);
}
它的基类MulticastDelegate 有三个关键字段:
-
target 是一个 Object 类型,如果包装的方法是静态的,这个字段就是null,如果包装的实例方法,这个字段就是实例的引用。
-
methodPtr 是一个内部整数值,CLR 用它标识回调方法。
-
_invocationList 是Object 类型,构造委托链时它引用一个委托数组。
上面的C# 语句 fb = getsomthing;,实际上会被编译器转为 Feedback fb = new Feedback(getsomthing);
在委托调用回调方法时:fb(321),实际上是调用 委托实例的 invoke 方法,这个方法的返回值和参数与 getsomthing 一致,它使用私有变量 _target 和 _methodPtr 在指定对象上调回调方法。
委托链
基类 Delegate 中有两个重要的方法,一个是Combine 另一个是 Remove,是构造委托链时使用的。
上面的语句 fb+=getanything; 编译器会转为fb = (Feedback)Delegate.Combine(fb , new Feedback(getanything));
Combine 方法传递两个参数,一个是fb ,另一个 是新的委托实例。分三种情况:
-
如果fb 是null,Combine 直接返回 参数2。
-
如果fb 不是null,Combine 创建一个新的委托实例,并将_invocationList 初始化为一个委托数组,第一个元素是参数1,第二个元素是参数2.
-
如果fb 不是null,第一个参数也是一个包含委托数组的委托实例,Combine也会创建一个新的委托实例,并将它的 _invocationList 初始化成一个委托数组,元素的前几个是参数1的委托数组元素,最后是参数2

图是CLR via C# 中截取的,fb被赋值新的委托实例后,原来的就被垃圾回收处理了。
当C# 遇到fb-=getanything; C#编译器会转为这样:fb = (Feedback)Delegate.Remove(fb , new Feedback(getanything));
Remove 方法传递也是两个参数,Remove 方法的内部有以下几个步骤:
-
首先扫描参数1 的委托数组,从尾到头。
-
如果委托数组中有参数2,就删除它,
-
删除后如果数组为空,返回null,
-
如果只剩一个,返回这个委托实例,
-
如果还剩多个,就再创建一个新的委托实例,将参数1的数组除去参数2,全部赋值到新委托实例中的数组中。
-
在调用 fb(321) 时会触发这个委托链。委托的invoke 方法是定义 Feedback 类型时就定义好了。下面是伪代码实现 invoke方法:
public void Invoke(Int32 value) {
Delegate[] delegateSet = _invocationList as Delegate[];
if (delegateSet != null) {
// This delegate's array indicates the delegates that should be called
foreach (Feedback d in delegateSet)
d(value); // Call each delegate
} else {
// This delegate identifies a single method to be called back
// Call the callback method on the specified target object.
_methodPtr.Invoke(_target, value);
// The preceding line is an approximation of the actual code.
// What really happens cannot be expressed in C#.
}
}
//如果是有返回值的回调方法,invoke 也被定义为有返回值,但是委托链调用后,只返回最后一个委托实例包装方法的返回值。
//这是为啥呢?看下面的 invoke方法的实现就知道了。不是我臆想的,它截取自 CLR via C#。
public Int32 Invoke(Int32 value) {
Int32 result;
Delegate[] delegateSet = _invocationList as Delegate[];
if (delegateSet != null) {
// This delegate's array indicates the delegates that should be called
foreach (Feedback d in delegateSet)
result = d(value); // Call each delegate
} else {
// This delegate identifies a single method to be called back
// Call the callback method on the specified target object.
result = _methodPtr.Invoke(_target, value);
// The preceding line is an approximation of the actual code.
// What really happens cannot be expressed in C#.
}
return result;
}
调用委托链时是顺序执行委托函数的,如果某个委托实例包含的方法运行出错了,上面的这种方法会导致,接下来的委托实例包含的方法得不到调用。
怎么办呢? 解决办法是从fb 实例中获取出委托数组。
MulticastDelegate 类提供了一个实例方法 GetInvocationList,用于显示调用委托数组中的每个委托。
public abstract class MulticastDelegate : Delegate {
// Creates a delegate array where each element refers
// to a delegate in the chain.
public sealed override Delegate[] GetInvocationList();
}
使用的方式如下:

事件
1、定义一个事件类型, 事件引发时,引发事件的对象,希望能向接收事件的对象发送一些附加信息。这些附加信息封装在自己的类中(可以定义字段、属性来实现)。
这种类应该从System.EventArgs 派生, 而且类名以 EventArgs 结束。
// Step #1: Define a type that will hold any additional information that
// should be sent to receivers of the event notification
internal class NewMailEventArgs : EventArgs {
private readonly String m_from, m_to, m_subject;
public NewMailEventArgs(String from, String to, String subject) {
m_from = from; m_to = to; m_subject = subject;
}
public String From { get { return m_from; } }
public String To { get { return m_to; } }
public String Subject { get { return m_subject; } }
}
2、定义事件成员
- 可访问性标识符 public 、 委托类型、名称;
- 用第一步定义好的事件类型,定义一个事件成员。
internal class MailManager {
// Step #2: Define the event member
public event EventHandler<NewMailEventArgs> NewMail;
...
}
3、定义负责引发事件的方法
protected virtual void OnNewMail(NewMailEventArgs e) {
EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);
if (temp != null) temp(this, e);
}
像上面这样写没有问题,但是用扩展泛型方法可以让所有的事件类型都能用该函数,只要它的参数是一个 EventHandler委托。
public static class EventArgExtensions {
public static void Raise<TEventArgs>(this TEventArgs e,
Object sender, ref EventHandler<TEventArgs> eventDelegate) {
// Copy a reference to the delegate field now into a temporary field for thread safety
EventHandler<TEventArgs> temp = Volatile.Read(ref eventDelegate);
// If any methods registered interest with our event, notify them
if (temp != null) temp(sender, e);
}
}
使用的时候如下所示:
protected virtual void OnNewMail(NewMailEventArgs e) {
e.Raise(this, ref m_NewMail);
}
4、实例化一个事件对象,并调用扩展方法,将事件发送出去,通过触发委托的方式。
internal class MailManager {
// Step #4: Define a method that translates the
// input into the desired event
public void SimulateNewMail(String from, String to, String subject) {
// Construct an object to hold the information we want
// to pass to the receivers of our notification ; 实例化一个 事件对象,它包含了事件该有的信息。
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
// Call our virtual method notifying our object that the event
// occurred. If no type overrides this method, our object will
// notify all the objects that registered interest in the event
OnNewMail(e);
}
}
// 1. A PRIVATE delegate field that is initialized to null
private EventHandler<NewMailEventArgs> NewMail = null;
// 2. A PUBLIC add_Xxx method (where Xxx is the Event name)
// Allows methods to register interest in the event.
public void add_NewMail(EventHandler<NewMailEventArgs> value) {
// The loop and the call to CompareExchange is all just a fancy way
// of adding a delegate to the event in a thread-safe way
EventHandler<NewMailEventArgs>prevHandler;
EventHandler<NewMailEventArgs> newMail = this.NewMail;
do {
prevHandler = newMail;
EventHandler<NewMailEventArgs> newHandler =
(EventHandler<NewMailEventArgs>) Delegate.Combine(prevHandler, value);
newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(
ref this.NewMail, newHandler, prevHandler);
} while (newMail != prevHandler);
}
// 3. A PUBLIC remove_Xxx method (where Xxx is the Event name)
// Allows methods to unregister interest in the event.
public void remove_NewMail(EventHandler<NewMailEventArgs> value) {
// The loop and the call to CompareExchange is all just a fancy way
// of removing a delegate from the event in a threadsafe way
EventHandler<NewMailEventArgs> prevHandler;
EventHandler<NewMailEventArgs> newMail = this.NewMail;
do {
prevHandler = newMail;
EventHandler<NewMailEventArgs> newHandler =
(EventHandler<NewMailEventArgs>) Delegate.Remove(prevHandler, value); newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, prevHandler);
} while (newMail != prevHandler); }
1 一个被初始化为null 的私有委托字段
internal sealed class Fax {
// Pass the MailManager object to the constructor
public Fax(MailManager mm) {
// Construct an instance of the EventHandler<NewMailEventArgs>
// delegate that refers to our FaxMsg callback method.
// Register our callback with MailManager's NewMail event
mm.NewMail += FaxMsg;
}
// This is the method the MailManager will call
// when a new email message arrives
private void FaxMsg(Object sender, NewMailEventArgs e) {
// 'sender' identifies the MailManager object in case
// we want to communicate back to it.
// 'e' identifies the additional event information
// the MailManager wants to give us.
// Normally, the code here would fax the email message.
// This test implementation displays the info in the console
Console.WriteLine("Faxing mail message:");
Console.WriteLine(" From={0}, To={1}, Subject={2}",e.From, e.To, e.Subject);
}
// This method could be executed to have the Fax object unregister
// itself with the NewMail event so that it no longer receives
// notifications
public void Unregister(MailManager mm) {
// Unregister with MailManager's NewMail event
mm.NewMail -= FaxMsg;
}
}