委托
在C#中,委托(delegate)是一种引用类型,在其他语言中,与委托最接近的是函数指针,但委托不仅存储对方法入口点的引用,还存储对用于调用方法的对象实例的引用。
using System;
using System.IO;
namespace DelegateAppl
{
class PrintString
{
static FileStream fs;
static StreamWriter sw;
// 委托声明
public delegate void printString(string s);
// 该方法打印到控制台
public static void WriteToScreen(string str)
{
Console.WriteLine("The String is: {0}", str);
}
// 该方法打印到文件
public static void WriteToFile(string s)
{
fs = new FileStream("c:\\message.txt", FileMode.Append, FileAccess.Write);
sw = new StreamWriter(fs);
sw.WriteLine(s);
sw.Flush();
sw.Close();
fs.Close();
}
// 该方法把委托作为参数,并使用它调用方法
public static void sendString(printString ps)
{
ps("Hello World");
}
static void Main(string[] args)
{
printString ps1 = new printString(WriteToScreen);
printString ps2 = new printString(WriteToFile);
sendString(ps1);
sendString(ps2);
Console.ReadKey();
}
}
}
使用方法:
delegate <return type> <delegate-name> <parameter list>
像是类一样进行声明。
printString ps1 = new printString(WriteToScreen)
在上述代码中,委托进行实例化。
sendString(ps1);
委托的实例化对象像是参数一样传入到函数之中。
ps("Hello World");
实现委托中封装的方法。
为什么要使用委托?
我理解的用途感觉和多态的动态链接有些相似,我不知道我可能选择用哪种方法实现。在多态当中,是不确定对象的实际类别。
比如上述代码,在**sendString()**中首先明确我是要对“Hello World进行操作哦”, 但是我可能有多种对于"Hello world的方法"。可能是打印在屏幕上,可能是打印在文件里面。
试着想一下,如果我们都在一个函数当中实现这些功能,我们要在传入参数的时候,用一个简单的标记告诉要实现哪种方法,然后使用if,else语句进行实现,和case的感觉有一些像。
首先上述的实现方法,将功能都做到一个函数之中,不具有可拓展性,不易于理解,其次如果每个方法有非常复杂的代码,那么我们最好将其封装到一个函数当中。
通俗理解,我们将我们的函数封装成了一个参数,进入其他的函数实现委托封装的功能。看上去我们像是一个大功能由多个小功能组成,但是注意的是,委托的声明有指定类型,我们在调用委托的时候只能对它声明时候限定的类型进行操作。
比如一只龙虾,你有刺身,清蒸,烘烤三种吃法,吃法是委托,三种吃法都是委托的实例化。但是他们都是对于龙虾的操作,所以在食用函数中,以一种吃法作为参数传递,然后将龙虾作为吃法的参数。
所以和我们最开始所说的,它是对方法的引用,同时也是对实例化的引用。
委托的另一个比较的用途在下面的事件介绍。
事件
由于平时很少做开发,但我做过小程序,事件应该是类似于button,点击的话调用相关的函数。
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
using System;
namespace SimpleEvent
{
using System;
/***********发布器类***********/
public class EventTest
{
private int value;
public delegate void NumManipulationHandler();
public event NumManipulationHandler ChangeNum;
protected virtual void OnNumChanged()
{
if ( ChangeNum != null )
{
ChangeNum(); /* 事件被触发 */
}else {
Console.WriteLine( "event not fire" );
Console.ReadKey(); /* 回车继续 */
}
}
public EventTest()
{
int n = 5;
SetValue( n );
}
public void SetValue( int n )
{
if ( value != n )
{
value = n;
OnNumChanged();
}
}
}
/***********订阅器类***********/
public class subscribEvent
{
public void printf()
{
Console.WriteLine( "event fire" );
Console.ReadKey(); /* 回车继续 */
}
}
/***********触发***********/
public class MainClass
{
public static void Main()
{
EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
subscribEvent v = new subscribEvent(); /* 实例化对象 */
e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
e.SetValue( 7 );
e.SetValue( 11 );
}
}
}
我们先脱离代码,想一下,如果定义一个事件需要哪些内容。
首先我们要知道谁使用这个事件,所以需要有订阅器,即使用对象。事件是需要行为触发的,不同的行为对应着不同的响应方法,所以我们首先要知道是谁的行为。这个被称之为发布器。事件建立在委托的基础上。
订阅器:我们还需要知道,需要什么处理方法,事件属于回调函数。
为什么发布器需要委托呢?我们回忆一下我,委托是将类的方法封装起来并且还可以实例化。我们事件的响应是需要根据触发的条件,比如一个button,是点击了一下,还是点击了两下,或者三下,都对应不同的方法,这非常适合符合委托的使用请况。
使用委托的另一个好处就是,委托可以看作一种类型,类型后面跟的是声明的事件,订阅器在注册的时候,即对应事件的某个触发,肯定是要对应的发布器和对应的订阅器相互响应,如果把委托看成一种类型,可以很好的进行约束。
比如发布事件在a类,(事件要发布在类中)订阅在b类,那么在c类中定义:
实例a = delegate (b.方法)。所以订阅器就是实现的方法,发布就是一个响应的接口。发布器是委托,响应的接口符合了委托的概念,往里面填充函数。
基于以上理解,可以试着理解一下上面的代码