深入“委托和事件”
一瓶牛奶和一张报纸引发的“委托”事件
委托的定义(What)
A delegate declaration defines a reference type that can be used to encapsulate a method with a specific signature. A delegate instance encapsulates a static or an instance method. Delegates are roughly similar to function pointers in C++; however, delegates are type-safe and secure.
MSDN Microsoft
C# delegate is a callback function. It is smarter then “standard” callback because it allows defining a strict list of parameters which are passed from class-server to class-client.
CodeProject
委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值
C# 编程指南
委托与C++函数指针区别
l 一个delegate object 一次可以搭载多个方法,而不是一次一个。当我们唤起一个搭载了多个方法的delegate,所有方法以其“被搭载到delegate object 的顺序”被依次唤起
l 一个delegate object 所搭载的方法并不需要属于同一个类。一个delegate object 所搭载的所有方法必须具有相同的原型和形式。然而,这些方法可以即有static 也有non-static,可以由一个或多个不同类的成员组成。
l 一个delegate type 的声明在本质上是创建了一个新的subtype instance,该subtype 派生自.NET library framework 的abstract base classes Delegate 或MulticastDelegate,它们提供一组public methods 用以询访delegate object 或其搭载的方法。
对委托的个人理解
委托是函数的封装,它代表一“类”函数。它们都符合一定的签名:拥有相同的参数列表、返回值类型。同时,委托也可以看成是对函数的抽象,是函数的“类”。此时,委托的实例将代表一个具体的函数。
为什么要使用委托?
更加灵活的方法调用
用于异步回调
多线程编程中使用委托来指定启动一个线程时调用的方法
C# 中的事件模型。用它们指明处理给定事件的方法
如何委托
<modifiers> delegate <return_type> <delegate_name> ( argument_list )
public delegate void Del (string message);
public void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
Del handler = new Del (obj.DelegateMethod);
handler("Hello World");
先声明委托,再实例化并指明所代具体方法
委托应用——异步回调
异步回调:由于实例化委托是一个对象,所以可以将其作为参数进行
传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参
数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的
进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使
用委托的代码无需了解有关所用方法的实现方面的任何信息。
public void MethodWithCallback(int param1, int param2, Del callback
{
callback("The number is: " + (param1 + param2).ToString());
}
MethodWithCallback(1, 2, handler);
回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。
委托实现回调类似于接口所实现的封装?
何时委托?何时接口?
在以下情况中使用委托:
当使用事件设计模式时。
当封装静态方法可取时。
当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。
需要方便的组合。
当类可能需要该方法的多个实现时。
在以下情况中使用接口:
当存在一组可能被调用的相关方法时。
当类只需要方法的单个实现时。
当使用接口的类想要将该接口强制转换为其他接口或类类型时。
【实例代码】
先看看使用接口的例子
//先定义两个接口用于报纸、牛奶,然后订阅者继承这个接口
public interface IMilkBox
{
void PutMilkIn(string s);
}
public interface INewsPaperBox
{
void PutNewsPaperIn(string s);
}
public class Subscriber : INewsPaperBox, IMilkBox
{
public string name;
public Subscriber(string name)
{
this.name = name;
}
public void PutNewsPaperIn(string s)
{
Console.WriteLine("{0}在报箱里收到送来的{1}", name, s);
}
public void PutMilkIn(string s)
{
Console.WriteLine("{0}在奶箱里收到送来的{1}", name, s);
}
}
public class NewsPaperSender
{
//接口实现
protected ArrayList subscribers = new ArrayList();
public void SendNewsPaper()
{
foreach (INewsPaperBox newsPaperBox in subscribers)
newsPaperBox.PutNewsPaperIn("报纸");
}
public void Subscribe(INewsPaperBox newsPaperBox)
{
subscribers.Add(newsPaperBox);
}
}
public class MilkSender
{
//接口实现
protected ArrayList subscribers = new ArrayList();
public void SendMilk()
{
foreach (IMilkBox milkBox in subscribers)
milkBox.PutMilkIn("牛奶");
}
public void Subscribe(IMilkBox milkBox)
{
subscribers.Add(milkBox);
}
}
//功能的实现
public static void Main ( string[] args )
{
//接口实现
MilkSender ms = new MilkSender();
NewsPaperSender nps = new NewsPaperSender();
Subscriber s1 = new Subscriber("张三");
Subscriber s2 = new Subscriber("李四");
Subscriber s3 = new Subscriber("王五");
ms.Subscribe(s1);
ms.Subscribe(s2);
nps.Subscribe(s2);
nps.Subscribe(s3);
ms.SendMilk();
nps.SendNewsPaper();
}
这里我们看看利用接口实现存在的问题:不能达到箱子的共享,每个接口都只能实现特定功能
再看看利用委托实现牛奶和报纸的订阅来解决上面的问题:使用委托让牛奶报纸共享箱子
namespace myDelegate
{
public delegate void SendableDelegate(string s);
public class Subscriber
{
public string name;
public Subscriber(string name)
{
this.name = name;
}
public void PutNewsPaperIn(string s)
{
Console.WriteLine("{0}在报箱里收到送来的{1}", name, s);
}
public void PutMilkIn(string s)
{
Console.WriteLine("{0}在奶箱里收到送来的{1}", name, s);
}
}
public class NewsPaperSender
{
//委托实现
public event SendableDelegate SendNewsPaperHandler;
public void SendNewsPaper()
{
if (SendNewsPaperHandler != null)
SendNewsPaperHandler("报纸");
}
public void Subscribe(SendableDelegate sd)
{
SendNewsPaperHandler += sd;
}
}
public class MilkSender
{
//委托实现
public event SendableDelegate SendMilkHandler;
public void SendMilk()
{
if (SendMilkHandler != null)
SendMilkHandler("牛奶");
}
public void Subscribe(SendableDelegate sd)
{
SendMilkHandler += sd;
}
}
//功能的实现
public class Client
{
public static void Main ( string[] args )
{
//委托实现
MilkSender ms = new MilkSender();
NewsPaperSender nps = new NewsPaperSender();
Subscriber s1 = new Subscriber("张三");
Subscriber s2 = new Subscriber("李四");
Subscriber s3 = new Subscriber("王五");
ms.Subscribe(new SendableDelegate(s1.PutMilkIn));
ms.Subscribe(new SendableDelegate(s2.PutMilkIn));
nps.Subscribe(new SendableDelegate(s2.PutNewsPaperIn));
nps.Subscribe(new SendableDelegate(s3.PutMilkIn));
ms.SendMilk();
nps.SendNewsPaper();
}
}
}
多播委托
多播委托——引用多个方法的委托,它连续调用每个方法。委托必须是同类型的,返回类型必须是void,不能带输出参数(可以带引用参数)。多播委托应用于事件模型中。
最后看看异步回调和多播的例子
class Program
{
public delegate void del (string msg);
static void Main (string[] args)
{
DelClass myDel=new DelClass();
del handle = new del (myDel.printMsg);
handle("hello world");
DelHandle myHandle = new DelHandle();
handle = new del (myHandle.hangleMsg);
handle("handle msg");
//异步回调
printAllMsg("call", "back", handle);
//多播
del d1 = myDel.method1;
del d2 = myDel.method2;
del d3 = myDel.printMsg;
del allMethod = d1 + d2;
allMethod += d3;
allMethod("all method ");
Console.ReadLine();
}
private static void printAllMsg(string param1, string param2, del myCallBack)
{
myCallBack(param1 + param2);
}
}
public class DelClass
{
public void printMsg(string msg)
{
Console.WriteLine(msg);
}
public void method1(string msg)
{
Console.WriteLine(msg+"method1");
}
public void method2(string msg)
{
Console.WriteLine(msg + "method2");
}
}
public class DelHandle
{
public void hangleMsg(string msg)
{
Console.WriteLine(msg);
}
}
最后大家可以参考下面这个例子,来加深对委托和事件的印象:
讲故事谈.NET委托:一个C#睡前故事:http://blog.youkuaiyun.com/lne818/archive/2006/08/28/1133533.aspx