以一个列子开始了解委托更容易些,下面是委托的使用方式:
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; } }