委托揭秘
看到button.Click+=new EventHandler(button1_Click);又忘记其中间过程了
委托我始终是学一遍,忘一遍,一个原因是我没用经常用道它,久而久之就忘记了,另一个原因是因为我没有深入的学习它。今天所以我决定用心搞明白它。这也是必备知识。
首先我们看一个例子
Internal delegate void Feedback(Int32 Value);
pulbic sealed class Program
{
public static void Main()
{
StsticDelegateDemo();
InstanceDelegateDemo();
ChainDelegateDemo1(new Program());
ChainDelegateDemo2(new Program());
}
Private static void StaticDelegateDemo()
{
Cosole.WriteLine("----Static Deleage Demo------");
Counter(1,3,null);
Counter(1,3,new Feedback(Program.FeedbackToConsole));
Counter(1,3,new Feedback(FeedbackToMsgBox));
Console.WriteLine();
}
Private static void InstanceDelegateDemo()
{
Cosole.WriteLine("----Instance Delegate Demo------");
Program p=new Program();
Counter(1,3,new Feedback(p.FeedbackToFile));
Console.WriteLine();
}
Provate static void ChainDelegateDemo()
{
Cosole.WriteLine("----Chain Delegate Demo------");
Feedback fb1=new Feedback(FeedbackToConsole);
Feedback fb2=new Feedback(FeedbackToMsgBox);
Feedback fb3=new Feedback(p.FeedbackToFile);
Feedback fbChain=null;
fbChain=(Feedback)Delegate.Combine(fbChain,fb1);
fbChain=(Feedback)Delegate.Combine(fbChain,fb2);
fbChain=(Feedback)Delegate.Combine(fbChain,fb3);
Counter(1,2,fbChain);
Console.WriteLine();
fbChain=(Feedback)Delegate.Remove(fbChain,new Feedback(FeedbackToMsgBox));
Cunter(1,2,fbChain);
}
private static void ChainDelegateDemo2()
{
Cosole.WriteLine("----Chain Delegate Demo2------");
Feedback fb1=new Feedback(FeedbackToConsole);
Feedback fb2=new Feedback(FeedbackToMsgBox);
Feedback fb3=new Feedback(p.FeedbackToFile);
Feedback fbChain=null;
fbChain+=fb1;
fbChain+=fb2;
fbChain+=fb3;
Counter(1,2,fbChain);
Console.WriteLine();
fbChain-=new Feedback(FeedbackToMsgBox);
Cunter(1,2,fbChain);
}
private static void Counter(Int32 from,Int32 to,Feedback fb)
{
for(Int32 val=from;val<=to;val++)
{
if(fb!=null)
fb(val);
}
}
private static void FeedbacktoConsole(Int32 value)
{
Console.WriteLine("Item="+value);
}
private static void FeedbackToMsgBox(Int32 value)
{
MessageBox.Show("Item="+value);
}
Private void FeedbackToFile(Int32 value)
{
StreamWriter sw=new StreamWriter("Status",true);
sw.WriteLine("Item="+value);
sw.Close();
}
}
一个委托要制定一个回调方法的签名。这里回调是指什么?就是调用函数。首先是用委托回调静态方法FeedbacktoConsole(Int32 value)和FeedbacktoMsgBox(Int32 value),在StaticDelegateDemo中调用Counter方法,第二次调用时,在方法调用的第三个参数传入一个新构造的Feedback委托对象。这个委托对象是一个方法的封装,这样,该方法就会通过封装期间间接进行回调。回调实例方法类似就不在多说了。
下面我们详细看看
internal delegate void Feedback(Int32 value);
其实看到这样的代码我们应该把它看成一个类( 编译器会像下面定义一个完整的类)代码 //MulticastDelegate 是一个特殊类。编译器和其他工具可以从此类派生,但是您不能显式地从此类进行派生。Delegate 类也是如此
注意:MulticastDelegate 是一个特殊类。编译器和其他工具可以从此类派生,但是您不能显式地从此类进行派生。Delegate 类也是如此
internal class Feedback:System.MulticastDelegate
{
public Feedback(Object object,IntPtr method);
//方法的原型与源代码相同
public virtual void Invoke(Int32 value);
public virtual IAsyncResult BeginInvoke(Int32
value,AsyncCallback callback,Object object);
public virtual void EndInvoke(IAsyncResult result);
}
这里有个构造函数,对象的引用被传值给该函数的object参数,一个特殊的标识方法的IntPtr值(从元数据标记获得)被传值给method参数。如果是静态方法那么null值就传给object。在构造函数内部,这两个参数分别保存在_target和_methodPtr私有字段内。
另外介绍MulticastDelegate3个重要的非公共字段
_target System.Object 当委托对象封装一个实例方法时,这个字段引用的是调用回调方法时要操作的对象。
_methodPtr System.IntPtr 一个内部的整数值,CLR用它标识要回调的方法。
_invocationList System.Object 该字段通常为null.在构造一个委托链时,它可以引用一个委托数组。
例:Feedback fbInstance=new Feedback(new Program().FeedbackToFile);
_target ------------->(Program对象)
fbInstance------>_methodPtr FeedbackToFile
_invocationList null
在看开始代码里有这一段代码,"if(fb!=null)fb(val);"这里没有名为fb的函数。因为编译器知道fb是一个引用一个委托对象的变量,所以会生成代码调用该委托对象的Invoke方法。即在编译器里是fb.Invoke(val),这里就相当于调用了原函数。
这里看到第三个字段,自然而然在心中就应该有一幅图委托数组指向很多像fbInstance这样的委托变量。例如调用上面介绍的fbChain的Invoke时,改委托就发现私有字段_invoationList不为null,就会遍历数组中所有元素,并调用每个委托封装的方法。
在来看最开始介绍的语句(button.Click+=new EventHandler(button1_Click);),这是由于C#为委托提供的语法便利造成的,这里介绍几个提供便利的语法
1.C#允许我们制定回调方法的名称,不必构造一个委托对象封装器。
internal sealed class AClass
{
public static void CallbackwithoutNewingAdelegateObjec
{
ThreadPool.QueueUserWorkItem(SomeAsyncTask,5);
}
private static void SomeAsyncTask(Object o)
{
Console.WriteLine(o);
}
}
ThreadPool类的静态方法期望接收到一个WaitCallback委托对象引用,该对象又包含一个SomeAsyncTask方法引用。由于编译器能自行进行推断,所以省略构造WaitCallback委托对象的代码,使整个代码可读性更强。其实正规写法应该为
ThreadPool.QueueUserWrokItem(new WaitCallback(SoneAsyncTask),5);
2.C#允许我们不需要定义回调方法
那么前面的代码可以写成
{
public static void CallbackWithoutNewingADelegateObject()
{
ThreadPool.QueueUserWorkItem(delegate(Object obj){Console.WriteLine(obj);},5)
}
}
当编译器看到期望收到委托对象引用的地方使用了delegate关键字就会自动在类中定义一个新的私有方法。
编译器会将代码改写成下面那样
{
[compoleerGenerated]
private sataic WaitCallback <>9_CacheedAnnymousMethodDeleagate1;
public sataic void CallbackWithoutnewingADeleateObject()
{
//第一次调用时,创建委托对象
if(<>9_CacheedAnnymousMethodDeleagate1==null)
{
<>9_CacheedAnnymousMethodDeleagate1=new WaitCllback(<CallbackwithoutNewingAdelegateObject>b_0);
}
ThreadPool.QueueUserWorkItem(<>9_CacheedAnnymousMethodDeleagate1,5);
}
[CopilerGenerated]
private static void <CallbackWithoutNewingADelegateObjec>b_0(Object obj)
{
Console.WriteLine(obj);
}
}
3.不需要指定回调方法参数
熟悉了1,2规则,那么我们很容易读懂下面代码了
button1.Click+=delegate(Object sender,EventArgs e){MessageBox,Show("the button was clicked");};
编译器可以允许我们不指定参数,所以可以写成
button1.Click+=delegate{MessageBox,Show("the button was clicked");};