本文系阅读了MSDN、《Windows Forms程序设计》中相关内容后总结归纳而成。
委托(delegate) 是一种数据结构,它引用静态方法或引用类实例及该类的实例方法。它声明定义一种引用类型,该类型可用于将方法用特定的签名封装。委托实例封装静态方法或实例方法。委托大致类似于 C++ 中的函数指针;但是,委托是类型安全和可靠的。
声明采用下列形式:
[attributes] [modifiers] delegate result-type identifier ([formal-parameters]);
其中:
attributes(可选)
附加的声明性信息。有关属性和属性类的更多信息,请参见 17. 属性。
modifiers(可选)
允许使用的修饰符有 new 和四个访问修饰符。
result-type
同方法的返回类型匹配的结果类型。
identifier
委托名称。
formal-parameters(可选)
参数列表。如果参数是一个指针,则必须用 unsafe 修饰符声明委托。
注意:
委托使您得以将函数作为参数传递。委托的类型安全要求作为委托传递的函数拥有同委托声明相同的签名。
一、接口
如果你定义了类A,但是你希望它的方法能和其他类共享——但很多情况下,共享是必要的,例如有10个不同的类都要实现该方法,重复地将这个方法声明10遍是无法令人接受的。
于是你将许多人的相同的愿望抽象为Interface(接口),如:
interface IObjectMove {
void MoveStarted();
void MoveProcessing();
int MoveCompleted();
}
这样,同样的方法可以被应用于多个地方,就像模板一般:
class A : IObjectMove{
public void MoveStarted() {......}
public void MoveProcessing() {......}
public int MoveCompleted(){
return 2; // maybe time to take
}
}
class B : IObjectMove{
public void MoveStarted() {......}
public void MoveProcessing() {......}
public int MoveCompleted() {
return 5; // maybe time to take
}
}
二、委托
A和B所实现的方法本身可能完全一样,但是A或许方法MoveProcessing()并不感兴趣。在这种情况下,接口的粒度不够小(创建过小的接口会造成资源浪费)。于是希望能够为每个方法创建一个小型接口,称之为委托(delegate):
delegate void MoveStarted();
delegate void MoveProcessing();
delegate int MoveCompleted();
在class c中有如下声明:
class c {
public void MoveStarted cMoveStarted;
public void MoveProcessing cMoveProcessing;
public int MoveCompleted cMoveCompleted;
}
如此一来,class A和class B的代码可能变成了这样:
class A {
public void MoveStarted() {......}
public int MoveCompleted() {
return 2; // maybe time to take
}
}
class B {
public void bKnowMoveStarted() {......}
}
在运行代码里需要实现委托,假设class A的一个实例(ample)需要实现一个委托MoveStarted,这个委托是class C的实例(notry)的一成员cMoveStarted。则可能需要通过赋值运算符来进行:
notry.cMoveStarted=new MoveStarted(ample.MoveStarted);
这很容易理解,但是这样做存在两个严重的隐患:
第一、作为class C的公共成员,cMoveStarted允许外来调用,安全性势必没有保障,class A和class B都有可能出现越界的行为,这是我们不愿意看到的。
第二、如果class B的实例eboy也需要实现同样的委托,则:
notry.bMoveStarted=new MoveStarted(eboy.bKnowMoveStarted);
很遗憾,这样产生的后果是class B的委托取代了class A的委托。
三、事件
理想的解决办法是通过注册和反注册函数来添加或移除自己的委托,而不允许直接对别人的委托进行操作。可以通过在class c声明成员时使用event关键字来实现:
//之前的声明是:public void MoveStarted cMoveStarted;
public event MoveStarted cMoveStarted;
public event MoveProcessing cMoveProcessing;
public event MoveCompleted cMoveCompleted;
有了event的约束,用户便只能通过+=和-=来添加和移除委托:
notry.cMoveStarted += new MoveStarted(ample.aKnowMoveStarted);
notry.cMoveStarted += new MoveStarted(eboy.bKnowMoveStarted); //最终是添加的效果而非取代
//移除
notry.cMoveStarted -= new MoveStarted(ample.aKnowMoveStarted);
notry.cMoveStarted -= new MoveStarted(eboy.bKnowMoveStarted);
四、总结
1、接口固然好,但始终关注它的粒度是否合适,应为一旦工程发布,修改或删除接口将是很危险的事情。
2、要使用事件(event)来约束委托行为。
附MSDN相关内容:
是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。委托的类型由委托的名称定义。下面的示例声明了一个名为 Del
的委托,该委托可以封装一个采用作为参数并返回 的方法。
C# | |
---|---|
public delegate void Del(string message); |
构造委托对象时,通常提供委托将包装的方法的名称或使用。实例化委托后,委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。例如:
C# | |
---|---|
// Create a method for a delegate. public static void DelegateMethod(string message) { System.Console.WriteLine(message); } |
// Instantiate the delegate. Del handler = DelegateMethod; // Call the delegate. handler("Hello World"); |