2009年9月30日星期三

一个演示自定义事件(event)的完整例子。

类Car中包含车的制造厂商CarMake,型号CarModel,日期CarYear,车主CarOwner。在这个类中,我们想自定义一个OwnerChanged事件,每当车主CarOwner改变的时候,自动发生这个车主变更事件。COSTOM_PARAM是个预定义开关,演示了两种实现模式,主要是是否用EventHandler这个系统定义好的的代理(delegate)还是自定义代理事件的区别。两种情况编译执行后都会在屏幕上输出:lin-lei这个字符串。

#undef COSTOM_PARAM //
using System;
namespace CustomEvents
{


public class Car
{
#if COSTOM_PARAM
public delegate void OwnerChangedEventHandler(string newOwner);
public event OwnerChangedEventHandler OwnerChanged;
#else

public event EventHandler OwnerChanged;
#endif

private string make;
private string model;
private int year;
private string owner;

public string CarMake
{
get { return this.make; }
set { this.make = value; }
}

public string CarModel
{
get { return this.model; }
set { this.model = value; }
}

public int CarYear
{
get { return this.year; }
set { this.year = value; }
}

public string CarOwner
{
get { return this.owner; }
set
{
this.owner = value;
if (this.OwnerChanged != null)
#if COSTOM_PARAM
this.OwnerChanged(value);
#else
this.OwnerChanged(this, new EventArgs());
#endif
}
}

public Car()
{
}
}

public class Test
{
#if COSTOM_PARAM
public static void car_OwnerChanged(string v)
{
//the CarOwner property has been modified
Console.WriteLine(v);
}
#else
public static void car_OwnerChanged(object sender, EventArgs e)
{
//the CarOwner property has been modified
Console.WriteLine(((Car)sender).CarOwner);
}
#endif

public static void Main()
{
Car car = new Car();

//adds an event handler to the OwnerChanged event
#if COSTOM_PARAM
car.OwnerChanged += new Car.OwnerChangedEventHandler(car_OwnerChanged);
#else
car.OwnerChanged += new EventHandler(car_OwnerChanged);
#endif
//setting this will fire the OwnerChanged event
car.CarOwner = "lin-lei";
}
}
}

在线程中访问窗体元素

两个Button ,都调用setText函数实现对TextBox1赋值,显示一个字符串Hello。

如果不用子线程,下面的代码可以完全正常的工作。不管点击button1,或者Button2都能正常在文本框中显示Hello.

private void button1_Click(object sender, EventArgs e)
{
setText();
}
private void button2_Click(object sender, EventArgs e)
{
setText();
}
private void setText()
{
textBox1.Text = "Hello";
}

但是,如果在Button2中另起一个子线程,访问textBox1,就会出现异常。

private void button1_Click(object sender, EventArgs e)
{
setText();
}
private void button2_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(setText));
t.Start();
}

private void setText()
{
textBox1.Text = "Hello";
}

为了实现在子线程中对窗体控件的访问,需要使用系统提供的Invoke方法,代码如下。

delegate void SetTextDelegate();

private void button1_Click(object sender, EventArgs e)
{
setText();
}
private void button2_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(setText2));
t.Start();
}

private void setText()
{
textBox1.Text = "Hello";
}

private void setText2()
{
Invoke(new SetTextDelegate(setText));
}

C#中还提供了this.InvokeRequired这个属性,来判断对控件的访问是主线程,还是子线程。应用这个属性,可以在button1_Click 和button2_Click中都统一用setText2,但是需要在setText2中增加一个判断,判断是主线程还是子线程的访问,代码如下。

private void button1_Click(object sender, EventArgs e)
{
setText2();
}
private void button2_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(setText2));
t.Start();
}

private void setText()
{
textBox1.Text = "Hello";
}

private void setText2()
{
if (this.InvokeRequired) //如果是子线程,需要用代理访问。
{
Invoke(new SetTextDelegate(setText));
}
Else //否则直接访问。
{
setText();
}
}

完整演示通过Delegate实现事件event的发布和接收

创建一个Clock类,并通过事件-event通知潜在的事件订阅者,每当经过一秒就通知一次。

/*
* 类 Clock   发布事件
* 代理类型 SecondChangeHandler -----<1>
* event 型的代理实例 SecondChange -----<2>
* 成员函数 OnSecondChange 产生事件,内部调用SecondChange
* 成员函数 Run,通过一个无限循环每经过一秒调用一次OnSecondChange,产生事件。
*
*  类 DisplayClock 接收事件并通过代理执行事件句柄
*    成员函数 Subscribe 将事件接收句柄函数通过SecondChangeHandler---<1> 传递个给 事件实例SecondChange---<2>
* 成员函数 TimeHasChanged 接收句柄函数 作为SecondChangeHandler---<1>的参数
*
* 类 LogClock 接收事件并通过代理执行事件句柄 和 DisplayClock 结构完全一致。
*
* 测试例程
* 创建接收事件的实例,并让他接收Subscribe 类Clock的实例。
*
*
*/
using System;
using System.Threading;

namespace SecondChangeEvent
{
/* ======================= Event Publisher =============================== */

// Our subject -- it is this class that other classes
// will observe. This class publishes one event:
// SecondChange. The observers subscribe to that event.
public class Clock
{
// Private Fields holding the hour, minute and second
private int _hour;
private int _minute;
private int _second;

// The delegate named SecondChangeHandler, which will encapsulate
// any method that takes a clock object and a TimeInfoEventArgs
// object as the parameter and returns no value. It's the
// delegate the subscribers must implement.
public delegate void SecondChangeHandler(
object clock,
TimeInfoEventArgs timeInformation
);

// The event we publish
public event SecondChangeHandler SecondChange;

// The method which fires the Event
protected void OnSecondChange(
object clock,
TimeInfoEventArgs timeInformation
)
{
// Check if there are any Subscribers
if (SecondChange != null)
{
// Call the Event
SecondChange(clock, timeInformation);
}
}

// Set the clock running, it will raise an
// event for each new second
public void Run()
{
for (; ; )
{
// Sleep 1 Second
Thread.Sleep(1000);

// Get the current time
System.DateTime dt = System.DateTime.Now;

// If the second has changed
// notify the subscribers
if (dt.Second != _second)
{
// Create the TimeInfoEventArgs object
// to pass to the subscribers
TimeInfoEventArgs timeInformation =
new TimeInfoEventArgs(
dt.Hour, dt.Minute, dt.Second);

// If anyone has subscribed, notify them
OnSecondChange(this, timeInformation);
}

// update the state
_second = dt.Second;
_minute = dt.Minute;
_hour = dt.Hour;

}
}
}

// The class to hold the information about the event
// in this case it will hold only information
// available in the clock class, but could hold
// additional state information
public class TimeInfoEventArgs : EventArgs
{
public TimeInfoEventArgs(int hour, int minute, int second)
{
this.hour = hour;
this.minute = minute;
this.second = second;
}
public readonly int hour;
public readonly int minute;
public readonly int second;
}

/* ======================= Event Subscribers =============================== */

// An observer. DisplayClock subscribes to the
// clock's events. The job of DisplayClock is
// to display the current time
public class DisplayClock
{
// Given a clock, subscribe to
// its SecondChangeHandler event
public void Subscribe(Clock theClock)
{
theClock.SecondChange +=
new Clock.SecondChangeHandler(TimeHasChanged);
}

// The method that implements the
// delegated functionality
public void TimeHasChanged(
object theClock, TimeInfoEventArgs ti)
{
Console.WriteLine("Current Time: {0}:{1}:{2}",
ti.hour.ToString(),
ti.minute.ToString(),
ti.second.ToString());
}
}

// A second subscriber whose job is to write to a file
public class LogClock
{
public void Subscribe(Clock theClock)
{
theClock.SecondChange +=
new Clock.SecondChangeHandler(WriteLogEntry);
}

// This method should write to a file
// we write to the console to see the effect
// this object keeps no state
public void WriteLogEntry(
object theClock, TimeInfoEventArgs ti)
{
Console.WriteLine("Logging to file: {0}:{1}:{2}",
ti.hour.ToString(),
ti.minute.ToString(),
ti.second.ToString());
}
}

/* ======================= Test Application =============================== */

// Test Application which implements the
// Clock Notifier - Subscriber Sample
public class Test
{
public static void Main()
{
// Create a new clock
Clock theClock = new Clock();

// Create the display and tell it to
// subscribe to the clock just created
DisplayClock dc = new DisplayClock();
dc.Subscribe(theClock);

// Create a Log object and tell it
// to subscribe to the clock
LogClock lc = new LogClock();
lc.Subscribe(theClock);

// Get the clock started
theClock.Run();
}
}
}

2009年9月29日星期二

multithread 多线程

C#中提供了4种方式实现多线程,各有优缺点

1.thread
可以暂停,重开,终止,但是反复的生成和终止线程会导致性能下降。
新开线程的函数中,不能有参数也不能有返回值。
2.ThreadPool 线程池
能高效的同时运行多个线程。
线程函数只能设置一个object型的参数。
不能有返回值。
线程的暂停,终止等控制困难。
同时能执行的线程的数量有限。
3.delegate 代理
函数可以有参数和返回值,和普通函数的调用无异,线程的终止,暂停等控制困难,同时执行的线程数有限制。
4.Timer
一定时间间隔执行函数,但是随同时执行的函数的增多,性能急剧下降。

下面分别用例子说明各个多线程的实现方式

例1 用thread类实现多线程

using System;
using System.Threading;

public class List1_1
{
public static void Main()
{
Thread threadA = new Thread(
new ThreadStart(ThreadMethod)); //

threadA.Start(); //

for(int i = 0; i < 100; i++)
{
Thread.Sleep(5);
Console.Write(" B ");
}
}

// 线程函数 注意:不能有参数也不能有返回值
private static void ThreadMethod()
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(5);
Console.Write(" A ");
}
}
}

由于线程函数不能传递参数,为应用带来了很大的不便,为了解决这个问题,实际应用中做一个线程类,利用类内部的属性字段来实现参数的传递,看下面的例子

using System;
using System.Threading;

public class List1_2
{
public static void Main()
{
ThreadedTextPrinter printer = new ThreadedTextPrinter(); //
printer.Print("A"); //

for (int i = 0; i < 100; i++)
{
Thread.Sleep(5);
Console.Write(" B ");
}
}
}

public class ThreadedTextPrinter //
{
private string text;

public void Print(string s) //
{
text = s; //

Thread thread = new Thread(new ThreadStart(ThreadMethod));
thread.Start();
}

//线程函数
private void ThreadMethod() //
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(5);
Console.Write(" {0} ", text); //
}
}
}


thread类的属性

thread.IsBackground = true; //后台执行
thread.Priority = ThreadPriority.Highest; //优先级别
thread.Suspend(); // 暂停
thread.Resume(); //继续
thread.Join();//等待到终了
thread.Abort();//强制终止
thread.Sleep(1000);//等待1秒

例2 ThreadPool 线程池

using System;
using System.Threading;

public class List2
{
public static void Main()
{
//为了让ThreadMethod在线程池中执行,需要定义WaitCallback型代理变量
WaitCallback waitCallback
= new WaitCallback(ThreadMethod); //

// 将线程函数登录进线程池
ThreadPool.QueueUserWorkItem(waitCallback, "A"); //
ThreadPool.QueueUserWorkItem(waitCallback, "B"); //

//在有按键输入前,程序一直运行。否则程序刚运行就结束,什么反应都没有。
Console.ReadLine();
}

// 线程函数 注意:可以有一个object型的参数,不能有返回值
private static void ThreadMethod(object state) //
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(5);
Console.Write(" {0} ", state);
}
}
}

线程池中同时执行的线程数有25的最大限制,ThreadPool.GetAvailableThreads可以返回还有多少线程可用=25-当前线程池中的线程的。

例3 用代理实现的多线程,这个例子理解起来比较难,在代码中加了很多注释。


//包含有返回值和参数的代理(delegate)
//这个代理是用来另起线程用的,所以在结构上和下面的线程函数ThreadMethod完全一致,参数和返回值相同。
delegate DateTime ThreadMethodDelegate(string c); //

static ThreadMethodDelegate threadMethodDelegate; //

public static void Main()
{
//用线程函数作为参数,生成一个代理的实例。
threadMethodDelegate
= new ThreadMethodDelegate(ThreadMethod); //

// 通过代理,开启线程调用,本程序的难点。
//BeginInvoke的参数介绍
//参数1:传给线程函数的参数,即ThreadMethod的参数。如果参数有多个可以顺序指定。
//参数2:异步回调函数,线程函数调用结束后,自动触发该回调函数的调用。
//参数3:object型的对象,可以自由指定。指定的对象作为回调函数的参数,同时也是BeginInvoke的返回值,所以回调函数的参数类型只能是IAsyncResult,如果不想用,可以用Null代替。
threadMethodDelegate.BeginInvoke(".",
new AsyncCallback(MyCallback), DateTime.Now); //

Console.ReadLine();
}

// 线程函数
private static DateTime ThreadMethod(string c)
{
//每过10毫米输出一个c,输出100次。
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10);
Console.Write(c);
}
return DateTime.Now;
}


// 线程处理结束后,自动触发执行的回调函数。
private static void MyCallback(IAsyncResult ar) //
{

//为了取得线程函数的返回值,调用EndInvoke()。
DateTime result = threadMethodDelegate.EndInvoke(ar); // ThreadMethod的返回值
//参见BeginInvoke()的第3个参数
DateTime beginTime = (DateTime)ar.AsyncState; // DateTime.Now

Console.WriteLine();
Console.WriteLine(
"{0} 处理开始、{1}处理结束。",
beginTime, result);
}



执行结果:

................................................................................
....................
2009/09/29 16:58:38 处理开始、2009/09/29 16:58:40处理结束。

例4 用Timer实现的多线程

using System;
using System.Threading;

public class List4
{
public static void Main()
{
//TimerCallback类型的回调函数,这个函数将会每经过一段时间被调用一次。
TimerCallback timerCallback
= new TimerCallback(ThreadMethod); //

// 显示当前时刻
Console.WriteLine(DateTime.Now);

//等待5秒,每2秒调用一次timerCallback
//调用一次timerCallback,等待5秒,然后每过2秒调用一次timerCallback。
//第二个参数 可以作为timerCallback的参数使用,object类型。
Timer timer = new Timer(
timerCallback, null, 5 * 1000, 2 * 1000); //

Console.ReadLine();
}

// 每2秒执行一次的线程函数
private static void ThreadMethod(object state) //
{
Console.WriteLine(DateTime.Now);
}
}

回调(Call back)

手机双向收费,座机接电话免费,座机想打电话给手机,只是让手机振铃把电话号码告诉手机,手机根据电话号码Call Back座机,这就是回调名称的来源。
为了调用函数A,将函数A作为参数传递给函数B,让B来调用(Call Back)函数A。

B(函数指针 p)
{
call p
};

mai()
{
A()
{
...
}
B(A);
}


这样做的优点是B函数可以根据需要,调用不同的函数A1,A2...,另外回调的最主要应用是实现异步调用(Asynchronous),在上面的示例中,如果A函数的处理非常花费时间,直接调用A会导致程序失去响应,但是通过回调,由于B能迅速返回,A会单独起一个线程。

下面是一个C#中完整的用delegate实现的Call Back,DBManager类实现实时列举所有的数据库活动链接(假设它运行在服务器端),显然希望客户端在列举活动链接的时候能迅速返回,同时服务器端提供给客户端回调函数callback,让客户端自由的决定显示方式。
 
using System;

class DBConnection
{
public DBConnection(string name)
{
this.name = name;
}

protected string Name;
public string name
{
get
{
return this.Name;
}
set
{
this.Name = value;
}
}
}

class DBManager
{
static DBConnection[] activeConnections;
public void AddConnections()
{
activeConnections = new DBConnection[5];
for (int i = 0; i < 5; i++)
{
activeConnections[i] = new DBConnection("DBConnection " + (i + 1));
}
}

public delegate void EnumConnectionsCallback(DBConnection connection);
public static void EnumConnections(EnumConnectionsCallback callback)
{
foreach (DBConnection connection in activeConnections)
{
callback(connection);
}
}
}

class Delegate1App
{
public static void ActiveConnectionsCallback(DBConnection connection)
{
Console.WriteLine("Callback method called for "
+ connection.name);
}

public static void Main()
{
DBManager dbMgr = new DBManager();
dbMgr.AddConnections();

DBManager.EnumConnectionsCallback myCallback = new DBManager.EnumConnectionsCallback(ActiveConnectionsCallback);

DBManager.EnumConnections(myCallback);
}
}


编译,执行后的输出结果:

Callback method called for DBConnection 1
Callback method called for DBConnection 2
Callback method called for DBConnection 3
Callback method called for DBConnection 4
Callback method called for DBConnection 5

2009年9月28日星期一

Delegate 代理和事件驱动 Event Driven

按下键盘上的一个建或者左击鼠标都会产生一个事件(Event),事件发生时的处理称作事件句柄(Event Handler)。GUI的程序都是针对用户做出的事件做相应的处理,这样的处理过程称作事件驱动(Event Driven)。C#中为了简化这种事件驱动程序的开发,提供了event关键字。event 本质上是属性机制的代理(delegate)版本。

先给一个事件驱动机制的简单例子。每1秒钟显示一次当前时刻,同时响应键盘的输入,做停止显示,显示方式改变,退出程序等处理。

using System;
using System.Threading;
class TestEvent
{
// 时间的显示形式
const string FULL = "yyyy/dd/MM hh:mm:ss\n";
const string DATE = "yyyy/dd/MM\n";
const string TIME = "hh:mm:ss\n";

static bool isAlive = true; // 程序的执行/终了标志
static bool isSuspended = true; // 程序暂停标志
static string timeFormat = TIME; //时间的表示形式

static void Main()
{
Thread thread = new Thread(new ThreadStart(EventLoop));
thread.Start();

while(isAlive)
{
if(!isSuspended)
{
// 每过1秒,显示一次当前时刻。
DateTime t = DateTime.Now;
Console.Write(t.ToString(timeFormat));
}
Thread.Sleep(1000);
}

thread.Join();
}

static void EventLoop()
{
// 事件循环
while(isAlive)
{
string line = Console.ReadLine();
char eventCode = line.Length == 0 ? '\0' : line[0];

// 事件处理
switch(eventCode)
{
case 'r': // run
isSuspended = false;
break;
case 's': // suspend
isSuspended = true;
break;
case 'f': // full
timeFormat = FULL;
break;
case 'd': // date
timeFormat = DATE;
break;
case 't': // time
timeFormat = TIME;
break;
case 'q': // quit
isAlive = false;
break;
default: // ヘルプ
Console.Write(
"操作方法\n" +
"r (run) : 开始时间表示。\n" +
"s (suspend): 暂停时间表示。\n" +
"f (full) : 日期,时间表示。\n" +
"d (date) : 只表示日期。\n" +
"t (time) : 只表示时间。\n" +
"q (quit) : 程序终止。\n"
);
break;
}
}//while(isAlive)
}//Main
}

上面的例子中的EventLoop,这个事件循环处理,可以分成事件接收(Event Loop)和事件处理(Event Handler)两个部分。虽然事件处理每个程序都不一样,但是事件循环接收(Event Loop)却每个程序都一样,所以我们希望将这个部分独立出来,做成一个单独的类。

 
// 事件处理用代理
delegate void KeyboadEventHandler(char eventCode);

///
/// 等待键盘输入事件的类
///

class KeyboardEventLoop
{
KeyboadEventHandler onKeyDown;
Thread thread;

public KeyboardEventLoop(KeyboadEventHandler onKeyDown)
{
this.onKeyDown = onKeyDown;
}

///
/// 等待事件开始。
///

public void Start()
{
this.thread = new Thread(new ThreadStart(this.EventLoop));
this.thread.Start();
}

///
/// 等待事件修了
///

public void End()
{
this.thread.Abort();
}

///
/// 本线程的状态。
///

public bool IsAlive
{
get{return this.thread.IsAlive;}
}

///
/// 循环等待事件的到来。
///

void EventLoop()
{
try
{
//无限循环
while(true)
{
// 读入文字
string line = Console.ReadLine();
char eventCode = (line == null || line.Length == 0) ? '\0' : line[0];

// 事件处理通过代理,让具体的事件句柄去处理。
this.onKeyDown(eventCode);
}
}
catch(ThreadAbortException){}
}
}

这个类中从事件循环等待中独立出来的okKeyDown这个代理,就是事件处理句柄(Event Handler)。用上面的这个类重新改写开始的例子程序。
 
class TestEvent
{
// 时刻的表示形式
const string FULL = "yyyy/dd/MM hh:mm:ss\n";
const string DATE = "yyyy/dd/MM\n";
const string TIME = "hh:mm:ss\n";

static KeyboardEventLoop eventLoop;
static bool isSuspended = true; // 程序的暂停标志
static string timeFormat = TIME; // 时刻的表示形式。

static void Main()
{
eventLoop = new KeyboardEventLoop(new KeyboadEventHandler(OnKeyDown));
eventLoop.Start();

while(eventLoop.IsAlive)
{
if(!isSuspended)
{
// 每过1秒,显示一次当前时刻。
DateTime t = DateTime.Now;
Console.Write(t.ToString(timeFormat));
}
Thread.Sleep(1000);
}
}
///
/// 事件处理部分。
///

static void OnKeyDown(char eventCode)
{
// 事件处理
switch(eventCode)
{
case 'r': // run
isSuspended = false;
break;
case 's': // suspend
Console.Write("\n暂停\n");
isSuspended = true;
break;
case 'f': // full
timeFormat = FULL;
break;
case 'd': // date
timeFormat = DATE;
break;
case 't': // time
timeFormat = TIME;
break;
case 'q': // quit
eventLoop.End();
break;
default: // ヘルプ
Console.Write(
"操作方法\n" +
"r (run) : 开始时间表示。\n" +
"s (suspend): 暂停时间表示。\n" +
"f (full) : 日期,时间表示。\n" +
"d (date) : 只表示日期。\n" +
"t (time) : 只表示时间。\n" +
"q (quit) : 程序终止。\n"
);
break;
}
}
}

再进一步,我们不但把事件循环接受的机制独立出来,同时还想能自由的追加和删除事件处理句柄(event handler),我们首先想到的是将这个代理变量KeyboadEventHandler onKeyDown;变成Pulic型,但是这样破坏了类的封装机制(外部操作内部),或者想通过属性机制(get/set)的方式来实现,但是这样也不行。因为我们希望对代理的调用,只能局限在类内部,而从类外部,只能对代理变量进行追加/消除操作。为了实现这个机制,C#中专门提供了event 关键字。在声明delegate型变数的时候,只要在最前面加上event关键字,这样的delegate变数就具备上面的功能,内部可以像普通的代理变量一样使用,而类的外部这只能使用-=,+=。
用event 关键字重新改写上面的例子,只是在KeyboadEventHandler OnKeyDown;前面加上pulic event,其他基本一样。

using System;
using System.Threading;
// 事件处理用代理
delegate void KeyboadEventHandler(char eventCode);

///
/// 等待键盘输入事件。
///

class KeyboardEventLoop
{
Thread thread;

public KeyboardEventLoop() : this(null){}

public KeyboardEventLoop(KeyboadEventHandler onKeyDown)
{
this.OnKeyDown += onKeyDown;
}

///
/// 键盘输入处理句柄。
///

public event KeyboadEventHandler OnKeyDown;

///
/// 循环等待开始。
///

public void Start()
{
this.thread = new Thread(new ThreadStart(this.EventLoop));
this.thread.Start();
}

///
/// 循环等待终了。
///

public void End()
{
this.thread.Abort();
}

///
/// 本线程的状态。
///

public bool IsAlive
{
get{return this.thread.IsAlive;}
}
///
/// 事件等待循环。
///

void EventLoop()
{
try
{
while(true)
{
// 等待键盘输入
string line = Console.ReadLine();
char eventCode = (line == null || line.Length == 0) ? '\0' : line[0];

// 事件的处理通过代理专递给其他具体的函数。
if(this.OnKeyDown != null)
this.OnKeyDown(eventCode);
}
}
catch(ThreadAbortException)
{
}
}
}

C# 语言中的代理 Delegate详解

C# 语言中的代理 Delegate详解

简单的说,Delegate就是C语言中的指向函数的指针,不同之处就是Delegate要比C的指向函数的指针功能更丰富,Delegate可以引用Instance Method(实例方法)并且可以同时引用多个函数。

Delegate的声明

delegate 返回值类型 Delegate类型名(参数序列);

注意,这里定义的【Delegate类型名】和普通定义的用户类具有相同的性质,并且缺省是继承自System.Delegate。所有和返回值类型相同的函数都可以作为参数带入到这个类中,下面看一个具体的例子:

例1

using System;

// MyDelegate
delegate void MyDelegate(int a);

class DelegateTest
{
static void Main()
{
// MyDelegate 这个类中把函数名作为参数带入
MyDelegate a = new MyDelegate(A); //在C#2.0中可以直接写成 MyDelegate a = A;

a(123); // 通过代理调用函数,在这里就是调用A(123)
}

static void A(int n)
{
Console.Write("A({0})调用成功。\n", n);
}
}


执行结果:A 123 调用成功。


下面看一个Instance Mothod调用的例子

例2

using System;

///
/// 消息显示代理
///

delegate void ShowMessage();

class Person
{
string name;
public Person(string name){this.name = name;}
public void ShowName(){Console.Write("姓名: {0}\n", this.name);}
};

class DelegateTest
{
static void Main()
{
Person p = new Person("小王");

//Instance Method 代入
ShowMessage show = new ShowMessage(p.ShowName);

show();
}
}

执行结果:
姓名 小王

多个方法同时调用的例子

例3

 
using System;

///
/// 消息显示代理
///

delegate void ShowMessage();

class DelegateTest
{
static void Main()
{
ShowMessage a = new ShowMessage(A);
a += new ShowMessage(B);
a += new ShowMessage(C);

a();
}

static void A(){Console.Write("A 被调用。\n");}
static void B(){Console.Write("B被调用。\n");}
static void C(){Console.Write("C 被调用。\n");}
}



执行结果:
A 被调用。
B 被调用。
C 被调用。
注: +=加入的方法被顺序调用。

Delegate还有一个很大的特点是实现非同期掉调用。
同期调用(Synchronous Call)是指函数调用后不能马上返回,只有函数执行完毕才能返回的调用。相反,非同期调用(Asynchronous Call)则在函数调用的瞬间就返回。所以,非同期调用可以用来实现多线程。
一旦定义了Delegate类型,C#的编译器就会自动生成BeginInvoke和EndInvoke这两个函数,用BeginInvoke开启非同期的调用,用EndInvoke来等待非同期调用的返回。
例如定义了一个 delegate int ShowMessage(int n, ref int p, out int q); 这样的Delegate ,编译器会自动生成下面的2个函数

IAsyncResult BeginInvoke(int n, ref int p, out int q, AsyncCallback callback, object state);
int EndInvoke(ref int p, out int q, IAsyncResult ar);

下面是给一个Delegate实现的非同期调用的例子
例4
 
using System;
using System.Threading;

namespace A
{
///
/// 消息显示代理
///

public delegate void ShowMessage(int n);

public class DelegateTest
{
static void Main()
{
const int N = 6;
ShowMessage asyncCall = new ShowMessage(AsynchronousMethod);

// asyncCall 非同期调用开启
IAsyncResult ar = asyncCall.BeginInvoke(N, null, null);

//这个循环部分会与asyncCall代理调用的函数同时执行。
for(int i=0; i {
Thread.Sleep(600);
Console.Write("Main ({0})\n", i);
}

// 等待 asyncCall 处理的结束
asyncCall.EndInvoke(ar);

Console.Write(" 处理结束 \n");
}

static void AsynchronousMethod(int n)
{
for(int i=0; i {
Thread.Sleep(1000);
Console.Write("AsynchronousMethod ({0})\n", i);
}
}

static void B(IAsyncResult ar){}
}
}



执行结果:

Main (0)
AsynchronousMethod (0)
Main (1)
Main (2)
AsynchronousMethod (1)
Main (3)
Main (4)
AsynchronousMethod (2)
Main (5)
AsynchronousMethod (3)
AsynchronousMethod (4)
AsynchronousMethod (5)
处理结束