{
MessageBox.Show("2");
}
{
this.UserSelect=true;
}
{
}
{
button1.Click+=new EventHandler(button2_Click);//实现弹出“1”“2”
}
{
MessageBox.Show("1");
}
{
button1.Click -= new EventHandler(button2_Click);//将“2”摘掉
}
本文出自 “王杰瑞的技术博客” 博客,请务必保留此出处http://wangjierui.blog.51cto.com/186879/42437
using System.Collections.Generic;
using System.Linq;
using System.Text;
{
public delegate void AllOverTheWorldSayHello(string name);
class Program
{
static void Main(string[] args)
{
Program pro = new Program();
AllOverTheWorldSayHello say1;
AllOverTheWorldSayHello say2;
say1 = pro.ChineseSayHello;
say2 = pro.AmericanSayHello;
pro.SayHello("李磊", say1);
pro.SayHello("MSLEE", say2);
//pro.SayHello("李磊",pro.ChineseSayHello);
//pro.SayHello("MSLee", pro.AmericanSayHello);
string ChineseName = "李磊", AmericanName = "MSLee";
ChinesePeople ch=new ChinesePeople();
AmericanPeople am=new AmericanPeople();
AllKindsOfPeople<ChinesePeople> chinese = new AllKindsOfPeople<ChinesePeople>(ch);
chinese.say(ChineseName);
AllKindsOfPeople<AmericanPeople> american = new AllKindsOfPeople<AmericanPeople>(am);
american.say(AmericanName);
Console.ReadLine();
}
protected void ChineseSayHello(string name)
{
Console.WriteLine(name+",你好");
}
protected void AmericanSayHello(string name)
{
Console.WriteLine(name+",Hello");
}
protected void SayHello(string name, AllOverTheWorldSayHello sayHello)
{
sayHello(name);
}
}
//-------------------------------------------------
/// <summary>
/// 泛型
/// </summary>
/// <typeparam name="T"></typeparam>
public class AllKindsOfPeople<T>
{
T obj;
public AllKindsOfPeople(T tObject)
{
this.obj = tObject;
}
public void say(string name)
{
if (obj is ChinesePeople)
{
(obj as ChinesePeople).say(name);
}
else
{
(obj as AmericanPeople).say(name);
}
}
}
//继承并实现
public class ChinesePeople:People
{
public void say(string name)
{
Console.WriteLine(name+",你好");
}
}
//继承并实现
public class AmericanPeople : People
{
public void say(string name)
{
Console.WriteLine(name+",Hello");
}
}
//定义一个接口
public interface People
{
void say(string name);
}
//---------------------------------------------------
}
本文出自 “微软技术” 博客,请务必保留此出处http://leelei.blog.51cto.com/856755/248511
“protected void Page_Load(object sender, EventArgs e)”这段代码相信没有人不熟悉的。细心一点一定会发现,非常多的事件方法都是带了“object sender, EventArgs e”这两个参数。这是不是和委托非常相似呢?
一、委托(有些书中也称为委派)
委托是什么呢?这个名字的意思已经赋予了我们想象的空间,你是编程的,你现在正在写一个ASP.NET网页,而JS是你不熟悉的,于是你委托你的一位同事来帮助你完成JS部分。这就是委托,把你所不能做的事情交给其他人去做。而怎么知道是哪个人去做呢?当然是要知道名字!而为了区别名字一样的不同人,因此,需要描述一个特征。
在C#中,委托的作用是这样描述的:委托就像一个函数的指针,在程序运行时可以使用它们来调用不同的函数。这个其实和你委托同事完成 JS代码一样。如果有两位同事可以做这件事情,他们只要做的结果能够满足你的需求(就像一个接口),尽管他们做的过程不一样,并且作出的效果也不一样,但是,能够达到你的要求就可以了。
1、简单的委托
那委托需要承载哪些信息呢?首先,它存储了方法名,还有参数列表(方法签名),以及返回的类型。比如:
delegate string/*返回类型*/ ProcessDelegate(int i);
这就是一个委托的定义。蓝色部分是声明委托的关键字,红色部分是返回的类型,而黑色部分是委托的类型名,和一个类名差不多,而()里的就是参数部分。它的意思是,你要使用这个委托来做事情的话,那么,做事情的方法必须满足以下条件:
1、返回类型和委托的返回类型一致,这里是string类型;
2、能且只能有一个参数,并且是int类型。
OK,满足以上两个条件,一切就可以工作了:)
例如:
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace TestApp
6 {
7 /// <summary>
8 /// 委托
9 /// </summary>
10 /// <param name="s1"></param>
11 /// <param name="s2"></param>
12 /// <returns></returns>
13 public delegate string ProcessDelegate(string s1, string s2);
14
15 class Program
16 {
17 static void Main(string[] args)
18 {
19 /* 调用方法 */
20 ProcessDelegate pd = new ProcessDelegate(new Test().Process);
21 Console.WriteLine(pd("Text1", "Text2"));
22 }
23 }
24
25 public class Test
26 {
27 /// <summary>
28 /// 方法
29 /// </summary>
30 /// <param name="s1"></param>
31 /// <param name="s2"></param>
32 /// <returns></returns>
33 public string Process(string s1,string s2)
34 {
35 return s1 + s2;
36 }
37 }
38 }
Text1Tex2
2、泛型委托
泛型的委托,就是然参数的类型不确定,例如代码改写为:
using System.Collections.Generic;
using System.Text;
namespace TestApp
{
/// <summary>
/// 委托
/// </summary>
/// <param name="s1"></param>
/// <param name="s2"></param>
/// <returns></returns>
public delegate string ProcessDelegate<T,S>(T s1, S s2);
class Program
{
static void Main(string[] args)
{
/* 调用方法 */
ProcessDelegate<string,int> pd = new ProcessDelegate<string,int>(new Test().Process);
Console.WriteLine(pd("Text1", 100));
}
}
public class Test
{
/// <summary>
/// 方法
/// </summary>
/// <param name="s1"></param>
/// <param name="s2"></param>
/// <returns></returns>
public string Process(string s1,int s2)
{
return s1 + s2;
}
}
}
输出的结果就是:
Text1100
泛型的详细内容不属于本文的介绍范围,这里不加多说了。
二、事件
在某件事情发生时,一个对象可以通过事件通知另一个对象。比如,前台完成了前台界面,他通知你,可以把前台和你开发的程序整合了。这就是一个事件。可以看出事件是在一个时间节点去触发另外一件事情,而另外一件事情怎么去做,他不会关心。就事件来说,关键点就是什么时候,让谁去做。
在C#中,时间定义关键字是event。例如:
event ProcessDelegate ProcessEvent;
整个事件定义方法以及执行过程:
using System.Collections.Generic;
using System.Text;
namespace TestApp
{
/// <summary>
/// 委托
/// </summary>
/// <param name="s1"></param>
/// <param name="s2"></param>
/// <returns></returns>
public delegate void ProcessDelegate(object sender, EventArgs e);
class Program
{
static void Main(string[] args)
{
/* 第一步执行 */
Test t = new Test();
/* 关联事件方法,相当于寻找到了委托人 */
t.ProcessEvent += new ProcessDelegate(t_ProcessEvent);
/* 进入Process方法 */
Console.WriteLine(t.Process());
Console.Read();
}
static void t_ProcessEvent(object sender, EventArgs e)
{
Test t = (Test)sender;
t.Text1 = "Hello";
t.Text2 = "World";
}
}
public class Test
{
private string s1;
public string Text1
{
get { return s1; }
set { s1 = value; }
}
private string s2;
public string Text2
{
get { return s2; }
set { s2 = value; }
}
public event ProcessDelegate ProcessEvent;
void ProcessAction(object sender, EventArgs e)
{
if (ProcessEvent == null)
ProcessEvent += new ProcessDelegate(t_ProcessEvent);
ProcessEvent(sender, e);
}
//如果没有自己指定关联方法,将会调用该方法抛出错误
void t_ProcessEvent(object sender, EventArgs e)
{
throw new Exception("The method or operation is not implemented.");
}
void OnProcess()
{
ProcessAction(this, EventArgs.Empty);
}
public string Process()
{
OnProcess();
return s1 + s2;
}
}
}
三、回调函数
打了这么多字,好累啊!
回调函数就是把一个方法的传给另外一个方法去执行。在C#有很多回调函数,比如异步操作的时候。这里先举个例子:
using System.Collections.Generic;
using System.Text;
namespace TestApp
{
/// <summary>
/// 委托
/// </summary>
/// <param name="s1"></param>
/// <param name="s2"></param>
/// <returns></returns>
public delegate string ProcessDelegate(string s1, string s2);
class Program
{
static void Main(string[] args)
{
/* 调用方法 */
Test t = new Test();
string r1 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process1));
string r2 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process2));
string r3 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process3));
Console.WriteLine(r1);
Console.WriteLine(r2);
Console.WriteLine(r3);
}
}
public class Test
{
public string Process(string s1,string s2,ProcessDelegate process)
{
return process(s1, s2);
}
public string Process1(string s1, string s2)
{
return s1 + s2;
}
public string Process2(string s1, string s2)
{
return s1 + Environment.NewLine + s2;
}
public string Process3(string s1, string s2)
{
return s2 + s1;
}
}
}
输出结果:
Text1Text2
Text1
Text2
Text2Text1
Process方法调用了一个回调函数,当然这里只执行了回调函数。可以看出,可以把任意一个符合这个委托的方法传递进去,意思就是说这部分代码是可变的。而设计上有一个抽离出可变部分代码的原则,这种用法无疑可以用到那种场合了。
全文完。http://birdshover.cnblogs.com Birdshover
using System;
using System.Threading;
//不需要构造函数的委托对象
internal sealed class NoConstructorDelegateClass
{
public static void CallbackWithoutNewingADelegateObject()
{
ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);
}
private static void SomeAsyncTask(Object o)
{
Console.WriteLine(o);
}
}
//不需要定义回调方法,生成一个一个静态委托字段,并在调用时实例化
internal sealed class NoCallbackMethodDelegateClass
{
public static void CallbackWithoutNewingADelegateOjbect()
{
ThreadPool.QueueUserWorkItem(delegate(Object obj) { Console.WriteLine(sm_name + obj); },5);
}
}
//不需要指定回调方法的参数
internal sealed class NoCallbackMethodAndParametersDelegateClass
{
public static void CallbackWithoutNewingADelegateOjbect()
{
ThreadPool.QueueUserWorkItem(delegate{ Console.WriteLine("Test"); }, 5);
}
}
//不需要将局部变量人工封装到类中,即可将它们传给一个回调方法 自动生成辅助类
internal sealed class NoEnLocalVarToClassDelegateClass
{
public static void UsingLocalVariablesInTheCallbackCode(Int32 numToDo)
{
Int32[] squares = new Int32[numToDo];
AutoResetEvent done = new AutoResetEvent(false);
for (Int32 n = 0; n < squares.Length; n++)
{
ThreadPool.QueueUserWorkItem(delegate(Object obj)
{
Int32 num = (Int32)obj;
squares[num] = num * num;
if (Interlocked.Decrement(ref numToDo) == 0)
done.Set();
}, n);
}
done.WaitOne();
for (Int32 n = 0; n < squares.Length; n++)
{
Console.WriteLine("Index {0},Square = [1]",n,squares[n]);
}
}
}
最近在学委托,经过摘录、整理,总结如下:
回调(Callback)函数是windows编程的一个重要部分。回调函数实际上是方法调用的指针,也成为函数指针,是一个非常强大的编程特性。.NET以委托的形式实现了函数的指针的概念。与C/C++的函数指针不同的是.NET委托是类型安全的。也就是说C/C++的函数指针只不过是一个指向内存单元的指针,我们无法知道这个指针实际指向什么,像参数和返回类型等就无从知晓了。
当把方法传送给其他方法时,需要用到委托。如考虑以下的函数:
C++:
#include <iostream>
#include <string>
using namespace std;
int fun(int);
int fun_call(int (*f)(int),int);
void main(int argc,char* argv[])
{
typedef int (*fp)(int);
fp fpt;
fpt=fun;
count<<fun_call(fpt,1);
}
int fun(int a)
{
return a-1;
}
int fun_call(int (*fp)(int),int b)
{
return (fp(10)+b);
}
上述程序的“ftp=fun”实现函数指针的初始化,直接将fun的地址赋给函数指针ftp,然后传送给fun_call,fun_call可以根据这两个参数计算出结果:fp(10)=9,9+1=10。实现了把方法传送给其他方法。
函数指针最常用的是使用函数指针数组来批量调用函数:
int f1(){return 1;}
int f2(){return 2;}
int f3(){return 3;}
void main(int argc,char* argv[])
{
tpyedef int (* fp)();
fp fps[3]={f1,f2,f3};
for(int 0;i<2;i++)
{
cout<<fps[i]<<endl; //实现按数组序列号调用函数
}
}
在编译时我们不知道第二个方法会是什么,这个信息只能在运行时得到,所以需要把第二个方法作为参数传递给第一个方法。在C/C++,只能提取函数的地址,并传送为一个参数。c是没有类型安全性的,可以把任何函数传送给需要函数指针的方法。这种直接的方法会导致一些问题,例如类型安全性,在面向对象编程中,方法很少是孤立存在的,在调用前通常需要与类实例相关联。而这种指针的方法没考虑这种情况。所以.NET在语法上不允许使用这种直接的方法。如果要传递方法,就必须把方法的细节封装在一种新的类型的对象中,这种新的对象就是委托。
委托,实际上只是一种特殊的对象类型,其特别之处在于,我们之前定义的所有对象都包含数据,而委托包含的只是函数的地址。
1、在c#中声明委托
delegate void Method(int x);
定义了委托就意味着告诉编译器这种类型的委托代表了哪种类型的方法,然后创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。也就是说,定义一个委托基本上是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在类的内部定义,也可以在类的外部定义。注意,委托是类型安全性非常高的,因此定义委托时,必须给出它所代表的方法签名和返回类型等全部细节。
2、在C#中使用委托
using System;
namespace DelegateSpace
{
class DelegateTest
{
private delegate string GetString();
static void Main()
{
Test test=new Test();
GetString method=new GetString(test.Add);
Console.WriteLine(method());
}
}
class Test
{
public string Add(int x,int y)
{
return (x+y).ToString();
}
}
}
上述程序中声明了类型为GetString的委托,并对它初始化,使它指向对象test的方法Add(int x,int y)。在C#中,委托在语法上总是带有一个参数的构造函数,这个参数就是委托指向的方法,这个方法必须匹配最初定义委托时的签名。如上例中委托是这样定义的:“delegate string GetString();”要求被委托的函数的返回类型是string,如果test.Add(int x,int y)返回的是int,则编译器就会报错。还要注意赋值的语句:“GetString method=new GetString(test.Add);“不能写成"GetString method=new GetString(test.Add(3,2));"因为test.Add(2,3)返回的是string。而委托的构造函数需要把传进的是函数的地址,这很像C/C++的函数指针。
3、多播委托
调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次显式调用这个委托。委托也可以包含多个方法。这种委托称为多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。所以,委托的签名必须返回void,否则,就只能得到委托调用的最后一个方法的结果。
如:
delegate void DoubleOp(double value);
class MainEntry
{
static void Main()
{
DoubleOp operations=MathOperation.MultiplyByTwo;
operations+=MathOperation.Square;
}
}
class MathOperation
{
public static double MultiplyByTwo(double value)
{
return value*2;
}
public static double Square(double value)
{
return value*value;
}
}
上面的“DoubleOp operation=MathOperation.MultiplyByTwo;operation+=MathOperation.Square;” 等价于“DoubleOp operation1=MathOperation.MultiplyByTwo;DoubleOp operation2=MathOperation.Square;DoubleOP operations=operation1+operation2;”,多播委托还可以识别运算符-和-=,用于从委托中删除方法调用。
通过一个多播委托调用多个方法还有一个大问题。多播委托包含一个逐个调用委托的集合。如果通过委托调用一个方法抛出异常,整个迭代就会终止。在这种情况下,为了避免这个问题,应手动迭代方法列表。可以使用Delegate类定义的方法GetInvocationList(),它返回一个Delegate对象数组。
如考虑以下代码:
public delegate void DemoDelegate();
class Program
{
static void One()
{
Console.WriteLine("One");
throw new Exception("Test!");
}
static void Two()
{
Console.WriteLine("Two");
}
static void Main()
{
DemoDelegate dl=One;
dl+=Two;
try
{
dl();
}
catch(Exception)
{
Console.WriteLine("Exception caught!");
}
}
}
运行结果:
One
Exception caught!
修改后的代码如下:
static void Main()
{
DemoDelegate dl=One;
dl+=Two;
Delegate[] delegates=dl.GetInvocationList();
foreach(DemoDelegate d in delegates)
{
try
{
d();
}
}
catch(Exception)
{
Console.WriteLine("Exception caught");
}
}
运行结果如下:
One
Exception caught
Two
同样地,如果委托签名不是返回void,但希望得到所有的经委托调用后的结果,也可以用GetInvocationList()得到Delegate对象数组,再用上面的迭代方式获得返回结果。
4、匿名方法
使用委托还有另外一种方式:通过匿名方法。匿名方法是用作委托参数的一个代码块。
如下代码:
using System;
namespace DelegateTest
{
class Program
{
delegate string delegateString(string val);
static void Main()
{
string mid=",middle part";
delegateString anonDel=delegate(string param)
{
param+=mid;
param+=" and end";
return param;
};
Console.WriteLine(anonDel("Strat of string"));
}
}
}
该代码块使用方法级的字符串变量mid,该变量是在匿名方法的外部定义的,并添加到要传送的参数中,接着代码返回该字符串值。匿名方法的优点是减少要编写的代码。不必定义仅由委托使用的方法。在为事件定义委托时,这是很显然的。这有助于减低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。
在使用匿名方法时,必须遵循一些规则:
1)在匿名方法中不能使用跳转语句跳到该匿名方法的外部;
2)匿名方法外部的跳转语句不能跳到该匿名方法的内部;
3)在匿名方法内部不能访问不安全代码,也不能访问在匿名方法外部使用的ref和out参数,但可以使用在匿名方法外部定义的其他变量。
5、λ表达式
这是C# 3.0为匿名方法提供的一个新方法。如前面的语句:
...
static void Main()
{
string mid=...;
delegateString anonDel=param=>
{
param+=mid;
param+=" and end";
return param;
};
...
}
...
λ表达式=>的左边列出了匿名方法需要的参数,右边列出了实现代码,实现代码放在花括号中,类似于前面的匿名方法,如果实现代码只有一行,可以删除花括号和return语句,编译器会自动添加该语句。
如:public delegate bool Predicate(int val);
Predicate pl=x=>x>5;
在上面的λ表达式中,左边定义了变量x,这个变量的类型自动设置为int,因为这是通过委托定义的,实现代码返回比较x>5布尔结果。如果x大于5,则返回true,否则返回false。
6、协变和抗变
委托调用的方法不需要与委托声明的定义类型相同。由此出现协变和抗变。
1)返回类型协变
方法的返回类型可以派生于委托定义的类型。如下代码:
public class DelegateReturn
{
}
public class DelegateReturn2:DelegateReturn
{
}
public delegate DelegaReturn MyDelegate1();
class Program
{
static void Main()
{
MyDelegate1 d1=Method1;
d1();
}
static DelegateReturn2 Method1()
{
DelegateReturn2 d2=new DelegateReturn2();
return d2;
}
}
上述代码中,委托MyDelegate定义为返回DelegateReturn类型。赋予委托实例d1的方法返回DelegateReturn2类型,DelegateReturn2派生自Delegate,根据子类“是”父类的这种关系,满足了委托的需求。这称为返回类型的协变。
2)参数类型的抗变
委托定义的参数可能不同于委托调用的方法,这里是返回类型不同,因为方法使用的参数类型可能派生自委托定义的类型。如下代码:
public class DelegateParam
{
}
public class DelegateParam2:DelegateParam
{
}
public delegate void MyDelegate2(DelegateParam2 p);
class Program
{
static void Main()
{
MyDelegate2 d2=Method2;
DelegateParam2 p=new DelegateParam2();
d2(p);
}
static void Method2(DelegateParam p)
{
}
}
上述代码中,委托使用的参数类型是DelegateParam2,而赋予委托实例d2的方法使用的参数类型是DelegateParam,DelegateParam是DelegateParam2的基类。
-------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSharp_Learn_001
{
//1 定义委托
public delegate void GreetingDelegate(string name);
class Program
{
//1.1 Func
private static void EnglishGreeting(string name)
{
Console.WriteLine("Moniring, " + name);
}
//1.2 Func
private static void ChineseGreeting(string name)
{
Console.WriteLine("早上好, " + name);
}
//2.1 DelegateFunc
private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
MakeGreeting(name);
}
//3. main
static void Main(string[] args)
{
GreetPeople("Jimmy Yang", EnglishGreeting);
GreetPeople("杨麒", ChineseGreeting);
Console.ReadKey();
}
}
}
本文出自 “咖啡时间” 博客,请务必保留此出处http://tuoxie174.blog.51cto.com/1446064/469103
-------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSharp_Learn_001
{
//1. 热水器
public class Heater{
private int temperature;
//1.1 声明委托
public delegate void BoilHandler(int param);
//1.2 声明事件
public event BoilHandler BoilEvent;
//1.3 烧水Action
public void BoilWater(){
for(int i=0; i<=100; i++){
temperature = i;
}
if(temperature > 95){
//1.3.1 如果有对象注册
if(BoilEvent != null){
//1.3.2 调用所有注册对象的方法
BoilEvent(temperature);
}
}
}
}
//2. 警报器
public class Alarm
{
public void MakeAlert(int param)
{
Console.WriteLine("Alarm: 滴滴滴, 水已经{0}度了", param);
}
}
//3. 显示器
public class Display
{
public static void ShowMsg(int param)
{
Console.WriteLine("Display: 水快烧开了,当前温度:{0}度.", param);
}
}
class Program{
static void Main(string[] args){
Heater heater= new Heater();
Alarm alarm= new Alarm();
//注册方法
heater.BoilEvent += alarm.MakeAlert;
heater.BoilEvent += (new Alarm()).MakeAlert;
heater.BoilEvent += Display.ShowMsg;
heater.BoilWater();
Console.ReadKey();
}
}
}
-------------------------------------------------
下面是btnRun的click事件:
- using System;
- using System.Drawing;
- using System.Collections;
- using System.ComponentModel;
- using System.Windows.Forms;
- using System.Data;
- namespace Events
- {
- /**//// <summary>
- /// Summary description for Form1.
- /// </summary>
- public class Form1 : System.Windows.Forms.Form
- {
- Counter oCounter = null;
- private System.Windows.Forms.Button cmdRun;
- private System.Windows.Forms.TextBox txtReachable;
- private System.Windows.Forms.TextBox txtCountTo;
- private System.Windows.Forms.Label label1;
- private System.Windows.Forms.Label label2;
- private System.Windows.Forms.Button btnRemoveDelegate;
- /**//// <summary>
- /// Required designer variable.
- /// </summary>
- private System.ComponentModel.Container components = null;
- public Form1()
- {
- //
- // Required for Windows Form Designer support
- //
- InitializeComponent();
- //
- // TODO: Add any constructor code after InitializeComponent call
- //
- oCounter = new Counter();
- oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached);
- oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached2);
- }
- /**//// <summary>
- /// Clean up any resources being used.
- /// </summary>
- protected override void Dispose( bool disposing )
- {
- if( disposing )
- {
- if (components != null)
- {
- components.Dispose();
- }
- }
- base.Dispose( disposing );
- }
- Windows Form Designer generated code#region Windows Form Designer generated code
- /**//// <summary>
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- /// </summary>
- private void InitializeComponent()
- {
- this.cmdRun = new System.Windows.Forms.Button();
- this.txtReachable = new System.Windows.Forms.TextBox();
- this.txtCountTo = new System.Windows.Forms.TextBox();
- this.label1 = new System.Windows.Forms.Label();
- this.label2 = new System.Windows.Forms.Label();
- this.btnRemoveDelegate = new System.Windows.Forms.Button();
- this.SuspendLayout();
- //
- // cmdRun
- //
- this.cmdRun.Location = new System.Drawing.Point(16, 72);
- this.cmdRun.Name = "cmdRun";
- this.cmdRun.Size = new System.Drawing.Size(48, 23);
- this.cmdRun.TabIndex = 2;
- this.cmdRun.Text = "Run";
- this.cmdRun.Click += new System.EventHandler(this.cmdRun_Click);
- //
- // txtReachable
- //
- this.txtReachable.Location = new System.Drawing.Point(144, 40);
- this.txtReachable.Name = "txtReachable";
- this.txtReachable.Size = new System.Drawing.Size(56, 20);
- this.txtReachable.TabIndex = 1;
- this.txtReachable.Text = "";
- //
- // txtCountTo
- //
- this.txtCountTo.Location = new System.Drawing.Point(144, 16);
- this.txtCountTo.Name = "txtCountTo";
- this.txtCountTo.Size = new System.Drawing.Size(56, 20);
- this.txtCountTo.TabIndex = 0;
- this.txtCountTo.Text = "";
- //
- // label1
- //
- this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(16, 16);
- this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(51, 13);
- this.label1.TabIndex = 3;
- this.label1.Text = "Count To";
- //
- // label2
- //
- this.label2.AutoSize = true;
- this.label2.Location = new System.Drawing.Point(16, 40);
- this.label2.Name = "label2";
- this.label2.Size = new System.Drawing.Size(99, 13);
- this.label2.TabIndex = 4;
- this.label2.Text = "Reach this number";
- //
- // btnRemoveDelegate
- //
- this.btnRemoveDelegate.Location = new System.Drawing.Point(16, 104);
- this.btnRemoveDelegate.Name = "btnRemoveDelegate";
- this.btnRemoveDelegate.Size = new System.Drawing.Size(168, 23);
- this.btnRemoveDelegate.TabIndex = 5;
- this.btnRemoveDelegate.Text = "Remove second handler";
- this.btnRemoveDelegate.Click += new System.EventHandler(this.btnRemoveDelegate_Click);
- //
- // Form1
- //
- this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
- this.ClientSize = new System.Drawing.Size(224, 134);
- this.Controls.AddRange(new System.Windows.Forms.Control[] {
- this.btnRemoveDelegate,
- this.label2,
- this.label1,
- this.txtCountTo,
- this.txtReachable,
- this.cmdRun});
- this.Name = "Form1";
- this.Text = "Events";
- this.ResumeLayout(false);
- }
- #endregion
- /**//// <summary>
- /// The main entry point for the application.
- /// </summary>
- [STAThread]
- static void Main()
- {
- Application.Run(new Form1());
- }
- private void btnRun_Click(object sender, System.EventArgs e)
- {
- if(txtCountTo.Text == "" || txtReachable.Text=="")
- return;
- oCounter.CountTo(Convert.ToInt32(txtCountTo.Text), Convert.ToInt32(txtReachable.Text));
- }
- private void oCounter_NumberReached(object sender, NumberReachedEventArgs e)
- {
- MessageBox.Show("Reached: " + e.ReachedNumber.ToString());
- }
- private void oCounter_NumberReached2(object sender, NumberReachedEventArgs e)
- {
- MessageBox.Show("Reached2: " + e.ReachedNumber.ToString());
- }
- private void btnRemoveDelegate_Click(object sender, System.EventArgs e)
- {
- oCounter.NumberReached -= new NumberReachedEventHandler(oCounter_NumberReached2);
- oCounter.CountTo(Convert.ToInt32(txtCountTo.Text), Convert.ToInt32(txtReachable.Text));
- }
- }
- }
Counter.cs
- using System;
- namespace Events
- {
- public delegate void NumberReachedEventHandler(object sender, NumberReachedEventArgs e);
- /**//// <summary>
- /// Summary description for Counter.
- /// </summary>
- public class Counter
- {
- public event NumberReachedEventHandler NumberReached;
- public Counter()
- {
- //
- // TODO: Add constructor logic here
- //
- }
- public void CountTo(int countTo, int reachableNum)
- {
- if(countTo < reachableNum)
- throw new ArgumentException("reachableNum should be less than countTo");
- for(int ctr=0;ctr<=countTo;ctr++)
- {
- if(ctr == reachableNum)
- {
- NumberReachedEventArgs e = new NumberReachedEventArgs(reachableNum);
- OnNumberReached(e);
- return;//don't count any more
- }
- }
- }
- protected virtual void OnNumberReached(NumberReachedEventArgs e)
- {
- if(NumberReached!=null)
- {
- NumberReached(this, e);
- }
- }
- }
- public class NumberReachedEventArgs : EventArgs
- {
- private int _reached;
- public NumberReachedEventArgs(int num)
- {
- this._reached = num;
- }
- public int ReachedNumber
- {
- get
- {
- return _reached;
- }
- }
- }
- }
-------------------------------------------------
.Net编程中最经常用的元素,事件必然是其中之一。无论在ASP.NET还是WINFrom开发中,窗体加载(Load),绘制(Paint),初始化(Init)等等。
“protected void Page_Load(object sender, EventArgs e)”这段代码相信没有人不熟悉的。细心一点一定会发现,非常多的事件方法都是带了“object sender, EventArgs e”这两个参数。这是不是和委托非常相似呢?
一、委托(有些书中也称为委派)
委托是什么呢?这个名字的意思已经赋予了我们想象的空间,你是编程的,你现在正在写一个ASP.NET网页,而JS是你不熟悉的,于是你委托你的一位同事来帮助你完成JS部分。这就是委托,把你所不能做的事情交给其他人去做。而怎么知道是哪个人去做呢?当然是要知道名字!而为了区别名字一样的不同人,因此,需要描述一个特征。
在C#中,委托的作用是这样描述的:委托就像一个函数的指针,在程序运行时可以使用它们来调用不同的函数。这个其实和你委托同事完成 JS代码一样。如果有两位同事可以做这件事情,他们只要做的结果能够满足你的需求(就像一个接口),尽管他们做的过程不一样,并且作出的效果也不一样,但是,能够达到你的要求就可以了。
1、简单的委托
那委托需要承载哪些信息呢?首先,它存储了方法名,还有参数列表(方法签名),以及返回的类型。比如:
delegate string/*返回类型*/ ProcessDelegate(int i);
这就是一个委托的定义。蓝色部分是声明委托的关键字,红色部分是返回的类型,而黑色部分是委托的类型名,和一个类名差不多,而()里的就是参数部分。它的意思是,你要使用这个委托来做事情的话,那么,做事情的方法必须满足以下条件:
1、返回类型和委托的返回类型一致,这里是string类型;
2、能且只能有一个参数,并且是int类型。
OK,满足以上两个条件,一切就可以工作了:)
例如:
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace TestApp
6 {
7 /// <summary>
8 /// 委托
9 /// </summary>
10 /// <param name="s1"></param>
11 /// <param name="s2"></param>
12 /// <returns></returns>
13 public delegate string ProcessDelegate(string s1, string s2);
14
15 class Program
16 {
17 static void Main(string[] args)
18 {
19 /* 调用方法 */
20 ProcessDelegate pd = new ProcessDelegate(new Test().Process);
21 Console.WriteLine(pd("Text1", "Text2"));
22 }
23 }
24
25 public class Test
26 {
27 /// <summary>
28 /// 方法
29 /// </summary>
30 /// <param name="s1"></param>
31 /// <param name="s2"></param>
32 /// <returns></returns>
33 public string Process(string s1,string s2)
34 {
35 return s1 + s2;
36 }
37 }
38 }
输出的结果是:
Text1Tex2
2、泛型委托
泛型的委托,就是然参数的类型不确定,例如代码改写为:
using System.Collections.Generic;
using System.Text;
namespace TestApp
{
/// <summary>
/// 委托
/// </summary>
/// <param name="s1"></param>
/// <param name="s2"></param>
/// <returns></returns>
public delegate string ProcessDelegate<T,S>(T s1, S s2);
class Program
{
static void Main(string[] args)
{
/* 调用方法 */
ProcessDelegate<string,int> pd = new ProcessDelegate<string,int>(new Test().Process);
Console.WriteLine(pd("Text1", 100));
}
}
public class Test
{
/// <summary>
/// 方法
/// </summary>
/// <param name="s1"></param>
/// <param name="s2"></param>
/// <returns></returns>
public string Process(string s1,int s2)
{
return s1 + s2;
}
}
}
输出的结果就是:
Text1100
泛型的详细内容不属于本文的介绍范围,这里不加多说了。
二、事件
在某件事情发生时,一个对象可以通过事件通知另一个对象。比如,前台完成了前台界面,他通知你,可以把前台和你开发的程序整合了。这就是一个事件。可以看出事件是在一个时间节点去触发另外一件事情,而另外一件事情怎么去做,他不会关心。就事件来说,关键点就是什么时候,让谁去做。
在C#中,时间定义关键字是event。例如:
event ProcessDelegate ProcessEvent;
整个事件定义方法以及执行过程:
using System.Collections.Generic;
using System.Text;
namespace TestApp
{
/// <summary>
/// 委托
/// </summary>
/// <param name="s1"></param>
/// <param name="s2"></param>
/// <returns></returns>
public delegate void ProcessDelegate(object sender, EventArgs e);
class Program
{
static void Main(string[] args)
{
/* 第一步执行 */
Test t = new Test();
/* 关联事件方法,相当于寻找到了委托人 */
t.ProcessEvent += new ProcessDelegate(t_ProcessEvent);
/* 进入Process方法 */
Console.WriteLine(t.Process());
Console.Read();
}
static void t_ProcessEvent(object sender, EventArgs e)
{
Test t = (Test)sender;
t.Text1 = "Hello";
t.Text2 = "World";
}
}
public class Test
{
private string s1;
public string Text1
{
get { return s1; }
set { s1 = value; }
}
private string s2;
public string Text2
{
get { return s2; }
set { s2 = value; }
}
public event ProcessDelegate ProcessEvent;
void ProcessAction(object sender, EventArgs e)
{
if (ProcessEvent == null)
ProcessEvent += new ProcessDelegate(t_ProcessEvent);
ProcessEvent(sender, e);
}
//如果没有自己指定关联方法,将会调用该方法抛出错误
void t_ProcessEvent(object sender, EventArgs e)
{
throw new Exception("The method or operation is not implemented.");
}
void OnProcess()
{
ProcessAction(this, EventArgs.Empty);
}
public string Process()
{
OnProcess();
return s1 + s2;
}
}
}
感觉到了什么?是不是和代码注入了差不多,相当于是可以用任意符合委托接口(委托确实很像接口)的代码,注入到Process过程。在他返回之前给他赋值。
三、回调函数
回调函数就是把一个方法的传给另外一个方法去执行。在C#有很多回调函数,比如异步操作的时候。这里先举个例子:
using System.Collections.Generic;
using System.Text;
namespace TestApp
{
/// <summary>
/// 委托
/// </summary>
/// <param name="s1"></param>
/// <param name="s2"></param>
/// <returns></returns>
public delegate string ProcessDelegate(string s1, string s2);
class Program
{
static void Main(string[] args)
{
/* 调用方法 */
Test t = new Test();
string r1 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process1));
string r2 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process2));
string r3 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process3));
Console.WriteLine(r1);
Console.WriteLine(r2);
Console.WriteLine(r3);
}
}
public class Test
{
public string Process(string s1,string s2,ProcessDelegate process)
{
return process(s1, s2);
}
public string Process1(string s1, string s2)
{
return s1 + s2;
}
public string Process2(string s1, string s2)
{
return s1 + Environment.NewLine + s2;
}
public string Process3(string s1, string s2)
{
return s2 + s1;
}
}
}
输出结果:
Text1Text2
Text1
Text2
Text2Text1
Process方法调用了一个回调函数,当然这里只执行了回调函数。可以看出,可以把任意一个符合这个委托的方法传递进去,意思就是说这部分代码是可变的。而设计上有一个抽离出可变部分代码的原则,这种用法无疑可以用到那种场合了
-------------------------------------------------
2011-04-18 13:48:50
如果 public delegate void MyDeletegate(string name);
本文出自 “crazy” 博客,请务必保留此出处http://cs2011.blog.51cto.com/2281851/547695
-------------------------------------------------
委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易。它们就像是一道槛儿,过了这个槛的人,觉得真 是太容易了。本文中,我将通过两个范例由浅入深地讲述什么是委托、为什么要使 用委托、事件的由来、.Net Framework中的委托和事件、委托和事件对Observer设计模式的意义,对它们的中间代码也做了讨论。
将方法作为方法的参数
我们先不管这个标题如何的绕口,也不管委托究竟是个什么东西,来看下面这两个最简单的方法,它们不过是在屏幕上输出一句问候的话语:
- public void GreetPeople(string name) {
- EnglishGreeting(name);
- }
- public void EnglishGreeting(string name) {
- Console.WriteLine("Morning, " + name);
- }
暂且不管这两个方法有没有什么实际意义。GreetPeople用于向某人问好,当我们传递代表某人姓名的name参数,比如说“xxx”,进 去的时候,在这个方法中,将调用EnglishGreeting方法,再次传递name参数,EnglishGreeting则用于向屏幕输出 “Morning, xxx”。
现在假设这个程序需要进行全球化,哎呀,不好了,我是中国人,我不明白“Morning”是什么意思,怎么办呢?好吧,我们再加个中文版的问候方法:
- public void ChineseGreeting(string name){
- Console.WriteLine("早上好, " + name);
- }
这时候,GreetPeople也需要改一改了,不然如何判断到底用哪个版本的Greeting问候方法合适呢?这就可以使用枚举。
- public enum Language{
- English, Chinese
- }
- public void GreetPeople(string name, Language lang){
- swith(lang){
- case Language.English:
- EnglishGreeting(name);
- break;
- case Language.Chinese:
- ChineseGreeting(name);
- break;
- }
- }
尽管这样解决了问题,但我不说大家也很容易想到,这个解决方案的可扩展性很差,如果日后我们需要再添加韩文版、日文版,就不得不反复修改枚举和GreetPeople()方法,以适应新的需求。
这就用到了委托。
本例中委托的定义:public delegate void GreetingDelegate(string name);
可以与上面EnglishGreeting()方法的签名对比一下,除了加入了delegate关键字以外,其余的是不是完全一样?
现在,让我们再次改动GreetPeople()方法,如下所示:
public void GreetPeople(string name, GreetingDelegate MakeGreeting){
MakeGreeting(name);
}
如你所见,委托GreetingDelegate出现的位置与 string相同,string是一个类型,那么GreetingDelegate应该也是一个类型,或者叫类(Class)。但是委托的声明方式和类却 完全不同,这是怎么一回事?实际上,委托在编译的时候确实会编译成类。因为Delegate是一个类,所以在任何可以声明类的地方都可以声明委托。更多的 内容将在下面讲述,现在,请看看这个范例的完整代码:
- namespace Delegate {
- //定义委托,它定义了可以代表的方法的类型
- public delegate void GreetingDelegate(string name);
- class Program {
- private static void EnglishGreeting(string name) {
- Console.WriteLine("Morning, " + name);
- }
- private static void ChineseGreeting(string name) {
- Console.WriteLine("早上好, " + name);
- }
- //注意此方法,它接受一个GreetingDelegate类型的方法作为参数
- private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
- MakeGreeting(name);
- }
- static void Main(string[] args) {
- GreetPeople("Zhang", EnglishGreeting);
- GreetPeople("小名", ChineseGreeting);
- Console.ReadKey();
- }
- }
- }
输出如下:
Morning, Zhang
早上好, 小名
我们现在对委托做一个总结:
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
将方法绑定到委托
1、不一定要直接在GreetPeople()方法中给 name参数赋值,我可以像这样使用变量:
static void Main(string[] args) {
string name1, name2;
name1 = "xxxxx";
name2 = "xx";
GreetPeople(name1, EnglishGreeting);
GreetPeople(name2, ChineseGreeting);
Console.ReadKey();
}
2、委托定义了一种参数类型,那么,也可以这么使用委托
static void Main(string[] args) {
GreetingDelegate delegate1, delegate2;
delegate1 = EnglishGreeting;
delegate2 = ChineseGreeting;
GreetPeople("fdf", delegate1);
GreetPeople("adads", delegate2);
Console.ReadKey();
}
3、可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。
static void Main(string[] args) {
GreetingDelegate delegate1;
delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
GreetPeople("dfag", delegate1);
Console.ReadKey();
}
4、也可以绕过GreetPeople方法,通过委托来直接调用EnglishGreeting和ChineseGreeting:
static void Main(string[] args) {
GreetingDelegate delegate1;
delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
delegate1 ("df");
Console.ReadKey();
}
5、也可以使用下面的代码来这样简化这一过程:
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting;
看到这里,应该注意到,这段代码第一条语句与实例化一个类是何其的相似,你不禁想到:上面第一次绑定委托时不可以使用“+=”的编译错误,或许可以用这样的方法来避免:
GreetingDelegate delegate1 = new GreetingDelegate();
delegate1 += EnglishGreeting;
delegate1 += ChineseGreeting;
但实际上,这样会出现编译错误: “GreetingDelegate”方法没有采用“0”个参数的重载。尽管这样的结果让我们觉得有点沮丧,但是编译的提示:“没有0个参数的重载”再次让我们联想到了类的构造函数。我知道你一定按捺不住想探个究竟,但再此之前,我们需要先把基础知识和应用介绍完。
既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是“-=”:
static void Main(string[] args) {
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
GreetPeople("ff", delegate1);
Console.WriteLine();
delegate1 -= EnglishGreeting; //取消对EnglishGreeting方法的绑定
// 将仅调用 ChineseGreeting
GreetPeople("zz", delegate1);
Console.ReadKey();
}
总结:
使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。
-------------------------------------------------
事件的由来
我们继续思考上面的程序:上面的三个方法都定义在Programe类中,这样做是为了理解的方便,实际应用中,通常都是 GreetPeople 在一个类中,ChineseGreeting和 EnglishGreeting 在另外的类中。现在你已经对委托有了初步了解,是时候对上面的例子做个改进了。假设我们将GreetingPeople()放在一个叫 GreetingManager的类中,那么新程序应该是这个样子的:
-
//定义委托,它定义了可以代表的方法的类型
-
public delegate void GreetingDelegate(string name);
-
//新建的GreetingManager类
-
public class GreetingManager{
-
public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
-
MakeGreeting(name);
-
}
-
}
-
class Program {
-
private static void EnglishGreeting(string name) {
-
Console.WriteLine("Morning, " + name);
-
}
-
private static void ChineseGreeting(string name) {
-
Console.WriteLine("早上好, " + name);
-
}
-
static void Main(string[] args) {
-
// ... ...
-
}
-
}
GreetingManager gm = new GreetingManager();
gm.GreetPeople(" Zhang", EnglishGreeting);
gm.GreetPeople("张 ", ChineseGreeting);
}
现在,假设我们需要使用委托将多个方法绑定到同一个委托变量,该如何做呢?让我们再次改写代码:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
GreetingDelegate delegate1;
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
gm.GreetPeople(" Zhang", delegate1);
}
到了这里,我们不禁想到:面向对象设计,讲究的是对象的封装,既然可以声明委托类型的变量(在上例中是delegate1),我们何不将这个变量封 装到 GreetManager类中?在这个类的客户端中使用不是更方便么?于是,我们改写GreetManager类,像这样:
public class GreetingManager{
//在GreetingManager类的内部声明delegate1变量
public GreetingDelegate delegate1;
public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
}
现在,我们可以这样使用这个委托变量:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.delegate1 = EnglishGreeting;
gm.delegate1 += ChineseGreeting;
gm.GreetPeople(" Zhang", gm.delegate1);
}
尽管这样做没有任何问题,但我们发现这条语句很奇怪。在调用gm.GreetPeople方法的时候,再次传递了gm的delegate1字段:
gm.GreetPeople(" Zhang", gm.delegate1);
既然如此,我们何不修改 GreetingManager 类成这样:
public class GreetingManager{
//在GreetingManager类的内部声明delegate1变量
public GreetingDelegate delegate1;
public void GreetPeople(string name) {
if(delegate1!=null){ //如果有方法注册委托变量
delegate1(name); //通过委托调用方法
}
}
}
在客户端,调用看上去更简洁一些:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.delegate1 = EnglishGreeting;
gm.delegate1 += ChineseGreeting;
gm.GreetPeople(" Zhang"); //注意,这次不需要再传递 delegate1变量
}
尽管这样达到了我们要的效果,但是还是存在着问题:
在这里,delegate1和我们平时用的string类型的变量没有什么分别,而我们知道,并不是所有的字段都应该声明成public,合适的做法是应该public的时候public,应该private的时候private。
我们先看看如果把 delegate1 声明为 private会怎样?结果就是:这简直就是在搞笑。因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为private了,客户端对它根本就不可见,那它还有什么用?
再看看把delegate1 声明为 public 会怎样?结果就是:在客户端可以对它进行随意的赋值等操作,严重破坏对象的封装性。
最后,第一个方法注册用“=”,是赋值语法,因为要进行实例化,第二个方法注册则用的是“+=”。但是,不管是赋值还是注册,都是将方法绑定到委托上,除了调用时先后顺序不同,再没有任何的分别,这样不是让人觉得很别扭么?
现在我们想想,如果delegate1不是一个委托类型,而是一个string类型,你会怎么做?答案是使用属性对字段进行封装。
于是,Event出场了,它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。
我们改写GreetingManager类,它变成了这个样子:
public class GreetingManager{
//这一次我们在这里声明一个事件
public event GreetingDelegate MakeGreet;
public void GreetPeople(string name) {
MakeGreet(name);
}
}
很容易注意到:MakeGreet 事件的声明与之前委托变量delegate1的声明唯一的区别是多了一个event关键字。看到这里,在结合上面的讲解,你应该明白到:事件其实没什么不好理解的,声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已。
为了证明上面的推论,如果我们像下面这样改写Main方法:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.MakeGreet = EnglishGreeting; // 编译错误1
gm.MakeGreet += ChineseGreeting;
gm.GreetPeople(" Zhang");
}
会得到编译错误:事件“Delegate.GreetingManager.MakeGreet”只能出现在 += 或 -= 的左边(从类型“Delegate.GreetingManager”中使用时除外)。
事件和委托的编译代码
这时候,我们注释掉编译错误的行,然后重新进行编译,再借助Reflactor来对 event的声明语句做一探究,看看为什么会发生这样的错误:
public event GreetingDelegate MakeGreet;
可以看到,实际上尽管我们在GreetingManager里将 MakeGreet 声明为public,但是,实际上MakeGreet会被编译成 私有字段,难怪会发生上面的编译错误了,因为它根本就不允许在GreetingManager类的外面以赋值的方式访问,从而验证了我们上面所做的推论。
我们再进一步看下MakeGreet所产生的代码:
private GreetingDelegate MakeGreet; //对事件的声明 实际是 声明一个私有的委托变量
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MakeGreet(GreetingDelegate value){
this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MakeGreet(GreetingDelegate value){
this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value);
}
现在已经很明确了:MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管是不是声明为public,它 总是被声明为private。另外,它还有两个方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托 类型的方法和取消注册。实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应remove_MakeGreet。而这两个方法的访问限制取决于声明事件时的访问限制符。
在add_MakeGreet()方法内部,实际上调用了System.Delegate的Combine()静态方法,这个方法用于将当前的变量添加到委托链表中。我们前面提到过两次,说委托实际上是一个类,在我们定义委托的时候:
public delegate void GreetingDelegate(string name);
当编译器遇到这段代码的时候,会生成下面这样一个完整的类:
public sealed class GreetingDelegate:System.MulticastDelegate{
public GreetingDelegate(object @object, IntPtr method);
public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object@object);
public virtual void EndInvoke(IAsyncResult result);
public virtual void Invoke(string name);
}
-------------------------------------------------
C#委托,事件理解
什么是委托?
委托和事件这两个概念是完全配合的。委托仅仅是函数指针,那就是说,它能够引用函数,通过传递地址的机制完成。委托是一个类,当你对它实例化时,要提供一个引用函数,将其作为它构造函数的参数。
每一个委托都有自己的签名,例如:Delegate int SomeDelegate(string s, bool b);是一个委托申明,在这里,提及的签名,就是说SomeDelegate 这个委托有 string 和 bool 类型的形参,返回一个int 类型。
上面提及的:当你对委托实例化时,要提供一个引用函数,将其作为它构造函数的参数。这里要注意了:被引用的这个函数必须和委托有相同的签名。
看下面的函数:
private int SomeFunction(string str, bool bln){...}
你可以把这个函数传给SomeDelegate的构造函数,因为他们有相似的签名(in other words,他们都有相同的形参类型和个数,并且返回相同的数据类型)。
SomeDelegate sd = new SomeDelegate(SomeFunction);
sd 引用了 SomeFunction,也就是说,SomeFunction已被sd所登记注册,如果你调用 sd,SomeFunction 这个函数也会被调用,记住:我所说 SomeFunction的含义,后面,我们会用到它。
现在,你应该知道如何使用委托了,让我们继续理解事件之旅……
事件的理解
我们知道,在C#中:
l 按钮(Button)就是一个类,当我们单击它时,就触发一次click事件。
l 时钟(Timer)也是一个类,每过一毫秒,就触发一次tick事件。
让我们通过一个例子来学习,假定有这样的情节:
现在有一个Counter的类,它有一个方法 CountTo(int countTo, int reachableNum),该方法表示:在指定的时间段内(0~~countTo),当到达指定的时间点reachableNum时,就触发一次NumberReached事件。
它还有一个事件:NumberReached,事件是委托类型的变量。意思是:如果给事件命名,用event关键字和要使用的委托类型申明它即可,如下所示:
public event NumberReachedEventHandler NumberReached;
在上面的申明中,NumberReachedEventHandle 仅是一个委托,更确切的表示应该是:NumberReachedDelegate。但是微软从不这样认为MouseDelegate或者PaintDelegate,,而是称谓:MouseEventHandler 或者 PaintEventHandler。所以
NumberReachedEventHandler 比NumberReachedDelegate听起来更方便一些,OK?好了,让我们继续,现在你知道了,在我们声明事件之前,需要象下面这样的形式来定义委托:
public delegate void NumberReachedEventHandler(object sender, NumberReachedEventArgs e);
现在声明的委托 NumberReachedEventHandle,它有一个void 返回值,和object,NumberReachedEventArgs两个形参。就像我们在第一节中强调的那样,当实例化委托时,作为实参传入的函数也必须拥有和委托同样的签名。
在你的代码中,你是否用过PaintEventArgs 或者 MouseEventArgs来确定鼠标的移动位置?是否在触发Paint事件的对象中用过Graphics 属性?实际上,为用户提供数据的类都是继承于System.EventArgs类,就是我们常说的事件参数类,如果事件不提供参数,就不定义该类。在我们的例子中,我们通过下面的类提供预期的时间点。
public class NumberReachedEventArgs : EventArgs
{
private int _reached;
public NumberReachedEventArgs(int num)
{
this._reached = num;
}
public int ReachedNumber
{
get
{
return _reached;
}
}
}
好,有了前面的介绍,让我们到Counter类里面看看:
namespace Events
{
public delegate void NumberReachedEventHandler(object sender,
NumberReachedEventArgs e);
/// <summary>
/// Summary description for Counter.
/// </summary>
public class Counter
{
public event NumberReachedEventHandler NumberReached;
public Counter()
{
//
// TODO: Add constructor logic here
//
}
public void CountTo(int countTo, int reachableNum)
{
if(countTo < reachableNum)
throw new ArgumentException(
"reachableNum should be less than countTo");
for(int ctr=0;ctr<=countTo;ctr++)
{
if(ctr == reachableNum)
{
NumberReachedEventArgs e = new NumberReachedEventArgs(
reachableNum);
OnNumberReached(e);
return;//don't count any more
}
}
}
protected virtual void OnNumberReached(NumberReachedEventArgs e)
{
if(NumberReached != null)
{
NumberReached(this, e);//Raise the event
}
}
}
在Counter中,如果到达指定的时间点,就触发一次事件,有以下几个方面需要注意:
l 通过调用NumberReached(它是NumberReachedEventHandler委托的实例)来完成一次触发事件。
NumberReached(this, e); 通过这种方式,可以调用所有的注册函数。
l 通过 NumberReachedEventArgs e = new NumberReachedEventArgs(reachableNum); 为所有的注册函数提供事件数据。
l 看了上面的代码,你可能要问了:为什么我们直接用 OnNumberReached(NumberReachedEventArgs e)方法来调用NumberReached(this,e),而不用下面的代码呢?
if(ctr == reachableNum)
{
NumberReachedEventArgs e = new NumberReachedEventArgs(reachableNum);
//OnNumberReached(e);
if(NumberReached != null)
{
NumberReached(this, e);//Raise the event
}
return;//don't count any more
}
这个问题问得很好,那就让我们再看一下OnNumberReached 签名:
protected virtual void OnNumberReached(NumberReachedEventArgs e)
①你也明白关键字protected限定了只有从该类继承的类才能调用该类中的所有方法。
②关键字 virtual 表明了在继承类中可以重写该方法。
这两点非常有用,假设你在写一个从Counter继承而来的类,通过重写OnNumberReached 方法,你可以在事件触发之前,进行一次其他的工作。
protected override void OnNumberReached(NumberReachedEventArgs e)
{
//Do additional work
base.OnNumberReached(e);
}
注意:如果你没有调用base.OnNumberReached(e), 那么从不会触发这个事件!在你继承该类而想剔出它的一些其他事件时,使用该方式是非常有用的。
l 还要注意到:委托 NumberReachedEventHandler 是在类定义的外部,命名空间内定义的,对所有类来说是可见的。
好,该我们来实际操作使用Counter类了。
在我们简单的应用程序中,我们有两个文本框,分别是:txtCountTo和txtReachable:
下面是btnRun的click事件:
private void btnRun_Click(object sender, System.EventArgs e)
{
if(txtCountTo.Text == "" || txtReachable.Text=="")
return;
oCounter.CountTo(Convert.ToInt32(txtCountTo.Text), Convert.ToInt32(txtReachable.Text));
}
private void oCounter_NumberReached(object sender, NumberReachedEventArgs e)
{
MessageBox.Show("Reached: " + e.ReachedNumber.ToString());
}
初始化事件处理的语法如下:
oCounter = new Counter();
oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached);
现在你明白了你刚才所做的一切,仅仅初始化 NumberReachedEventHandler 委托类型的对象(就像你实例化其他对象一样),注意到 oCounter_NumberReached 方法的签名与我前面提到的相似。
还要注意我们用的是+= 而不是=;这是因为委托是特殊的对象,它可以引用多个对象(在这里是指它可以引用多个函数)。For example 如果有另外一个
和oCounter_NumberReached一样具有相同签名的函数oCounter_NumberReached2,这两个函数都可以被引用:
oCounter = new Counter();
oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached);
oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached2);
现在,触发一个事件后,上面两个函数被依次调用。
视情况而定,如果你想让oCounter_NumberReached2在NumberReached事件发生后不再被调用,可以简单地这样写:oCounter.NumberReached -= new NumberReachedEventHandler(oCounter_NumberReached2);
最后
让我们看一下完整的源代码,以供参考:
Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace Events
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
Counter oCounter = null;
private System.Windows.Forms.Button cmdRun;
private System.Windows.Forms.TextBox txtReachable;
private System.Windows.Forms.TextBox txtCountTo;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button btnRemoveDelegate;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
oCounter = new Counter();
oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached);
oCounter.NumberReached += new NumberReachedEventHandler(oCounter_NumberReached2);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
Windows Form Designer generated code Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.cmdRun = new System.Windows.Forms.Button();
this.txtReachable = new System.Windows.Forms.TextBox();
this.txtCountTo = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.btnRemoveDelegate = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// cmdRun
//
this.cmdRun.Location = new System.Drawing.Point(16, 72);
this.cmdRun.Name = "cmdRun";
this.cmdRun.Size = new System.Drawing.Size(48, 23);
this.cmdRun.TabIndex = 2;
this.cmdRun.Text = "Run";
this.cmdRun.Click += new System.EventHandler(this.cmdRun_Click);
//
// txtReachable
//
this.txtReachable.Location = new System.Drawing.Point(144, 40);
this.txtReachable.Name = "txtReachable";
this.txtReachable.Size = new System.Drawing.Size(56, 20);
this.txtReachable.TabIndex = 1;
this.txtReachable.Text = "";
//
// txtCountTo
//
this.txtCountTo.Location = new System.Drawing.Point(144, 16);
this.txtCountTo.Name = "txtCountTo";
this.txtCountTo.Size = new System.Drawing.Size(56, 20);
this.txtCountTo.TabIndex = 0;
this.txtCountTo.Text = "";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(16, 16);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(51, 13);
this.label1.TabIndex = 3;
this.label1.Text = "Count To";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(16, 40);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(99, 13);
this.label2.TabIndex = 4;
this.label2.Text = "Reach this number";
//
// btnRemoveDelegate
//
this.btnRemoveDelegate.Location = new System.Drawing.Point(16, 104);
this.btnRemoveDelegate.Name = "btnRemoveDelegate";
this.btnRemoveDelegate.Size = new System.Drawing.Size(168, 23);
this.btnRemoveDelegate.TabIndex = 5;
this.btnRemoveDelegate.Text = "Remove second handler";
this.btnRemoveDelegate.Click += new System.EventHandler(this.btnRemoveDelegate_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(224, 134);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.btnRemoveDelegate,
this.label2,
this.label1,
this.txtCountTo,
this.txtReachable,
this.cmdRun});
this.Name = "Form1";
this.Text = "Events";
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void btnRun_Click(object sender, System.EventArgs e)
{
if(txtCountTo.Text == "" || txtReachable.Text=="")
return;
oCounter.CountTo(Convert.ToInt32(txtCountTo.Text), Convert.ToInt32(txtReachable.Text));
}
private void oCounter_NumberReached(object sender, NumberReachedEventArgs e)
{
MessageBox.Show("Reached: " + e.ReachedNumber.ToString());
}
private void oCounter_NumberReached2(object sender, NumberReachedEventArgs e)
{
MessageBox.Show("Reached2: " + e.ReachedNumber.ToString());
}
private void btnRemoveDelegate_Click(object sender, System.EventArgs e)
{
oCounter.NumberReached -= new NumberReachedEventHandler(oCounter_NumberReached2);
oCounter.CountTo(Convert.ToInt32(txtCountTo.Text), Convert.ToInt32(txtReachable.Text));
}
}
}
Counter.cs
using System;
namespace Events
{
public delegate void NumberReachedEventHandler(object sender, NumberReachedEventArgs e);
/// <summary>
/// Summary description for Counter.
/// </summary>
public class Counter
{
public event NumberReachedEventHandler NumberReached;
public Counter()
{
//
// TODO: Add constructor logic here
//
}
public void CountTo(int countTo, int reachableNum)
{
if(countTo < reachableNum)
throw new ArgumentException("reachableNum should be less than countTo");
for(int ctr=0;ctr<=countTo;ctr++)
{
if(ctr == reachableNum)
{
NumberReachedEventArgs e = new NumberReachedEventArgs(reachableNum);
OnNumberReached(e);
return;//don't count any more
}
}
}
protected virtual void OnNumberReached(NumberReachedEventArgs e)
{
if(NumberReached!=null)
{
NumberReached(this, e);
}
}
}
public class NumberReachedEventArgs : EventArgs
{
private int _reached;
public NumberReachedEventArgs(int num)
{
this._reached = num;
}
public int ReachedNumber
{
get
{
return _reached;
}
}
}
}
-------------------------------------------------
-------------------------------------------------
-------------------------------------------------