在C# 中,委托类似于C++中的回调机制,声明一个
委托类型,可以分别采用实例方法和静态方法实例化了委托,所不同的是采用静态方法实例化的委托,它的
Target
属性为
null
;而用实例方法实例化的委托,它的
Target
属性为该实例。但是这里要注意,如果你用
Console.WriterLine(delegateObj.Target)
输出该实例,结果是该实例的类型,而不是该实例的名字。
在
C#
中,有关键字
delegate
,又有类
Delegate
;我们可以用
delegate
来创建一个类
Delegate
的实例:
Delegate d = Delegate.CreateDelegate(Type type, object target, string method),
CreateDelegate()
方法有很多重载版本,视具体情况而定。这里要指出的就是,关于
delegate
的属性
Target
的就是类
Delegate
的公共属性
Target
。基于这一点,委托也可以动态创建,但必须提供相应的参数:委托的类型,类实例,委托要表示的实例方法的名称。
委托链,可以将几个(必需是同类型)委托给串接起来,当委托触发时,就会按照添加是的顺序依次调用各个委托回调函数;特别是,当委托回调函数有返回值,只有最后一个委托回调函数的返回值能被带回,之前的都被会被覆盖;如果要获取委托链中每一个回调函数的返回值,则可以使用
delegate.GetInvocationList()
方法
(
其中
delegate
是一个委托
)
,它将返回一个
Delegate
数组,然后遍历该数组,就得到每一个回调函数的返回值,不过要注意,虽然它返回的是
Delegate
数组,但是在遍历的时候,遍历元素的类型必须与
delegate
的类型一致,而不是
Delegate
类型,实例代码如下:
delegate String GetStatus();
Delegate[] arrayOfDelegates = status.GetInvocationList();
foreach (GetStatus getStatus in arrayOfDelegates)
{
try {
report.AppendFormat("{0}{1}{1}",
getStatus(), Environment.NewLine);
}
catch (Exception e) {
Object component = getStatus.Target;
report.AppendFormat(
"Failed to get status from {1}{2}{0} Error: {3}{0}{0}",
Environment.NewLine,
((component == null) ? "" : component.GetType() + "."),
getStatus.Method.Name, e.Message);
}
}
其中status的类型是GetStatus,而GetStatus就是一个委托类型。注意上述代码中getStatus 的类型是GetStatus,而不是Delegate类型。
同时,如果委托类型的返回值为
Class(
父类
)
,
存在一个返回值为
Subclass(
子类
)
的方法,那么这个方法也是可以用来初始化这个委托类型的,代码示例:
class Mammals
{
public Mammals()
{
Console.WriteLine("Mammals");
}
}
{
public Mammals()
{
Console.WriteLine("Mammals");
}
}
class Dogs : Mammals
{
public Dogs()
{
Console.WriteLine("Dogs");
}
}
{
public Dogs()
{
Console.WriteLine("Dogs");
}
}
class Program
{
// Define the delegate.
public delegate Mammals HandlerMethod();
{
// Define the delegate.
public delegate Mammals HandlerMethod();
public static Mammals FirstHandler()
{
return new Mammals();
}
{
return new Mammals();
}
public static Dogs SecondHandler()
{
return new Dogs();
}
{
return new Dogs();
}
static void Main()
{
HandlerMethod handler1 = FirstHandler;
{
HandlerMethod handler1 = FirstHandler;
handler1();
Console.WriteLine();
Console.WriteLine();
// Covariance allows this delegate.
HandlerMethod handler2 = SecondHandler;
handler2();
Console.ReadLine();
}
}
HandlerMethod handler2 = SecondHandler;
handler2();
Console.ReadLine();
}
}
输出结果为
Mammals
Mammals
Dogs
另外,如果委托类型的参数为
Subclass(
子类
)
,
存在一个参数为
Class(
父类
)
的方法,那么这个方法也是可以用来初始化一个委托类型的,代码示例如下:
class Mammals
{
public Mammals()
{
Console.WriteLine("Mammals");
}
}
{
public Mammals()
{
Console.WriteLine("Mammals");
}
}
class Dogs : Mammals
{
public Dogs()
{
Console.WriteLine("Dogs");
}
}
{
public Dogs()
{
Console.WriteLine("Dogs");
}
}
class Program
{
public delegate void HandlerMethod(Dogs sampleDog);
{
public delegate void HandlerMethod(Dogs sampleDog);
public static void FirstHandler(Mammals elephant)
{
}
{
}
public static void SecondHandler(Dogs sheepDog)
{
}
{
}
static void Main(string[] args)
{
// Contravariance permits this delegate.
HandlerMethod handler1 = FirstHandler;
handler1(new Dogs());
Console.WriteLine();
HandlerMethod handler2 = SecondHandler;
handler2(new Dogs());
Console.ReadLine();
}
}
输出结果为:
{
// Contravariance permits this delegate.
HandlerMethod handler1 = FirstHandler;
handler1(new Dogs());
Console.WriteLine();
HandlerMethod handler2 = SecondHandler;
handler2(new Dogs());
Console.ReadLine();
}
}
输出结果为:
Mammals
Dogs
Mammals
Dogs
另外一个问题是,如果在委托链中有一个回调函数抛出了异常,不会影响其他的回调函数的执行,就好像异常没有发生一样。
目前我们所见到的委托都是通过一个命名方法来声明一个委托,在
C#2.0
以后就引入了匿名方法来声明一个委托,其创建方法很简单,就是把代码块作为委托的参数来声明即可,如下:其中
d
就是一个用匿名方法实例化的委托。
delegate void Del(int x);
Del d = delegate(int k) { /* ... */ };
这种委托的好处就是通过避免写一个方法来减少代码量,其最常见的一个应用就是创建线程。示例如下:
System.Threading.Thread t1 = new System.Threading.Thread
(delegate()
{
System.Console.Write("Hello, ");
System.Console.WriteLine("World!");
});
t1.Start();
但是匿名方法又很多限制,比如在其代码块不可以使用
goto, break, continue
等从代码块中跳到代码块外面,反之也是不可以的。
命名与匿名方法代码混合使用,代码示例:
delegate void Printer(string s);
class TestClass
{
static void Main()
{
Printer p = delegate(string j)
{
System.Console.WriteLine(j);
};
p("The delegate using the anonymous method is called.");
p = new Printer(TestClass.DoWork);
p("The delegate using the named method is called.");
}
static void DoWork(string k)
{
System.Console.WriteLine(k);
}
}
事件:
事件用于类或结构体通知对象来处理一些事情,事件是对
delegate
的应用,而且声明一个事件的时候,必须提供一个
delegate
类型,如下形式:
public delegate void SampleEventDelegate(object sender, EventArgs e);
public class SampleEventSource
{
public event SampleEventDelegate SampleEvent;
}
通过delegate,事件可以封装类型安全的方法,并在被触发的时候调用。声明了一个事件后,可以注册多个方法到这个事件中,并且这些方法的参数类型、返回值,即signature必须一致。如在一个事件中同时注册里几个方法,那么可以通过事件的GetInvocationList()方法来返回一个Delegate数组,通过遍历这个数组,就可以得到所有回调函数(事件处理函数)的信息。事件的调用形式必须与其声明时的delegate的signature(签名)一致,如上面声明的事件SampleEvent,在调用的时候必须如下形式调用:SampleEvent(object, System.EventArgs.Empty)。