委托、匿名方法、委托的发布和订阅、事件的发布和订阅、EventHandler类、Windows事件概述
通常我们将数据对象(或其引用)作为方法的参数进行传递,而使用委托可以实现将方法本身作为参数进行传递。
当对象在运行过程中遇到一些特定的事情,通常需要使用事件来进行处理,事件可以使用.NET框架自身提供的,也可以实现用户自定义
1.委托
为了实现方法的参数化,提出了委托的概念。
1)概述
C#中委托是一种引用类型,只不过在委托对象的引用中存放的是对方法的引用。
委托声明语法格式如下:
【修饰符】 delegate 【返回类型】 【委托名称】 (【参数列表】) public delegate int MyDelegate(int x ,int y);
一个与委托相匹配的方法的参数必须与委托的参数相同,完全相同,且这二者的返回类型相同。
2)一个小demo
public class Test_Delegate { public int Add(int x, int y) { return x + y; } } class Program { public delegate int MyDelegate(int x, int y);//定义一个委托类型 static void Main(string[] args) { Test_Delegate td = new Test_Delegate(); MyDelegate md = td.Add;//创建委托类型的实例md,并绑定到Add方法 int sum = md(2, 3);//委托的调用 Console.WriteLine("result:" + sum); Console.Read(); } }
结果:
上面代码中MyDekegate自定义委托类型继承自System.MulticastDelete,且该自定义委托类型包含一个Invoke方法,该方法接受两个整形参数,并返回一个整数值,与Add方法完全相同。实际上程序在进行委托调用时就是调用了Invoke方法,所以上面的委托调用完全可以写成下面的这种形式:
int sum = md.Invoke(2, 3);//委托的调用
这种形式也更方便来理解。
这是最最简单的demo
class Program { static int Add(int x, int y) { return x + y; } public delegate int MyDelegate(int x, int y);//定义一个委托类型 static void Main(string[] args) { MyDelegate md = Add;//创建委托类型的实例md,并绑定到Add方法 int sum = md(2, 3);//委托的调用 Console.WriteLine("result:" + sum); Console.Read(); } }
2.匿名方法
为了简化委托的可操作性,在C#中提出了匿名方法的概念
1)概述
匿名方法允许一个与委托关联的代码被内联地写入使用委托的位置,这使得代码对与委托的实例很直接。匿名方法还共享了对本地语句包含的函数成员的访问。
使用匿名方法不必创建单独的方法。原先是委托绑定一个方法,现在可以直接将方法的代码块作为参数传给委托而不必调用方法。
匿名方法的语法格式如下:
delegate (【参数列表】) { 【代码块】 }; delegate (string j) { Console.WriteLine(j); };
2)一个小demo来体现两种的不同
delegate void DeleOutput(string s);//自定义委托类型 class Program { static void NamedMethod(string k)//与委托相匹配的命名方法 { Console.WriteLine(k); } static void Main(string[] args) { //委托的引用指向匿名方法 delegate(string j){} DeleOutput del = delegate (string j) { Console.WriteLine(j); }; del.Invoke("Anonymous Methods");//委托对象del调用匿名方法 //del("匿名方法被调用"); Console.Write(" "); del = NamedMethod;//委托绑定到NamedMethod del("Named Method");//委托对象del调用命名方法 //del.Invoke("NamedMethod被调用"); Console.ReadLine(); } }
结果:
3.委托的发布和订阅
1)概述
由于委托能够引用方法,而且能够链接和删除其他委托对象,因而就能够通过委托来实现事件的“发布和订阅”这两个必要的过程。通过委托来实现事件处理的过程需要以下4个步骤:
(1)定义委托类型,并在发布者类中定义一个该类型的公有成员
(2)在订阅者类中定义委托处理方法
(3)订阅者对象将其事件处理方法链接到发布者对象的委托成员(一个委托类型的引用)上
(4)发布者对象在特定的情况下激发委托操作,从而自动调用订阅者对象的委托处理方法
下面以学校铃声为例
2)小demo
public delegate void RingEvent(int ringKind);//声明一个委托类型 //定义一个委托发布者类SchoolRIng,并在该类中定义一个RingEvent类型的共有成员 //即委托成员,用来进行委托发布,然后定义一个成员方法Jow,用来实现激发委托操作 public class SchoolRing//定义发布者类 { public RingEvent OnbellSound;//委托发布 public void Jow(int ringKind)//实现打铃操作 { if (ringKind == 1 || ringKind == 2) { Console.Write(ringKind == 1 ? "Class Begin,":"Class Over,"); if (OnbellSound != null)//不等于空,说明它已经订阅了具体的方法 OnbellSound(ringKind);//回调OnBellSound委托所订阅的具体方法 else Console.WriteLine("ringKind is wrong!"); } } } //定义一个Student类来对听到铃声做相应的动作,在该类中定义一个铃声事件的处理方法SchoolJow //并在某一个激发时刻或状态链接到SchoolRing对象的OnBellSound的委托上。另外,在订阅完毕后 //通过CancelSubscribe方法删除订阅 public class Student//定义订阅者类 { public void SubscribeToRing(SchoolRing schoolRing)//学生订阅铃声这个委托事件 { schoolRing.OnbellSound += SchoolJow;//通过委托的链接操作进行订阅操作 } public void SchoolJow(int ringKind)//事件的处理方法 { if (ringKind == 2) { Console.WriteLine("Student rest"); } else if (ringKind == 1) { Console.WriteLine("Student learn"); } } public void CancelSubscribe(SchoolRing schoolRing)//取消订阅铃声动作 { schoolRing.OnbellSound -= SchoolJow; } } //当发布者SchoolRIng类的对象调用其Jow方法进行打铃时,就会自动调用Student对象的 //SchoolJow这个事件的处理方法 class Program { static void Main(string[] args) { SchoolRing sr = new SchoolRing(); Student st = new Student(); st.SubscribeToRing(sr);//学生订阅学校铃声 Console.WriteLine("Please input ringKind(1.Begin Class;2.End Class):"); sr.Jow(Convert.ToInt32(Console.ReadLine()));//开始打铃动作 Console.Read(); } }
result:
通过上面的这个实例,通过发布者来发布这个委托,然后定义一个事件触发器,在这个触发器被击发后(比如执行这个触发器方法),会调用这个委托,然后委托根据自身的订阅情况,再进行回调委托(事件)的处理方法,因为委托已经通过”+=“符号链接到该处理方法上了。
3.事件的发布和订阅
委托可以进行发布和订阅,从而使不同的对象对特定的情况作出反应。但这种机制存在一个问题,即外部对象可以任意修改已发布的委托(因为这个委托仅是一个普通的类级共有成员),这也会影响到其他对象对委托的订阅(使委托丢掉了其他的订阅),比如,在进行委托订阅的时候使用“=”符号,而不是“+=”,或者在订阅时,设置委托指向一个空引用,这些都对委托的安全性造成严重的威胁,如下面的示例代码所示:
public void SubscribeToRing(SchoolRing schoolRing)//学生订阅铃声这个委托事件 { //通过赋值运算符进行订阅,使委托OnbellSound丢掉了其他的订阅 schoolRing.OnbellSound = SchoolJow; }
public void SubscribeToRing(SchoolRing schoolRing)//学生订阅铃声这个委托事件 { schoolRing.OnbellSound = null;//取消委托订阅的所有内容 }
为了解决这个问题,C#提供了专门的事件处理机制,以保证事件订阅的可靠性,其做法是在发布委托的定义中加上event关键字,其他代码不变。例如:
public event RingEvent OnbellSound;//事件发布
在经过简单的修改后,其他类型再使用OnbellSound委托时,就只能够将其放在复合赋值运算符“+=”或“-=”的左侧,直接使用“=”运算符,编译系统会报错。例如,下面的代码都是错误的:
schoolRing.OnbellSound = null;//系统会报错的 schoolRing.OnbellSound = SchoolJow;//系统会报错的
事件是一种特殊的类型,发布者在发布一个事件后,订阅者对它只能作自身的订阅或取消,而不能干涉其他订阅者。
事件也是类的一种特殊成员:即使是公有事件,除了其所属类型之外,其他类型只能对其进行订阅或者取消,别的任何操作都是不允许的,因此时间具有特殊的封装性。和一般委托成员不同,某个类型的事件只能由自身触发。例如,在Student的成员方法中,使用如下代码来直接调用School对象的OnbellSound事件是不被允许的。比如“schoolRing.OnbellSound(2)”这个代码是错误的,因为OnbellSound这个委托只能在包含其自身定义的发布者类种被调用。
4.EventHandler类
1)概述
在事件发布和订阅的过程中,定义事件的类型(委托类型)是一件重复性的工作。为此.NET类库中定义了一个Eventhandler委托类型,并建议尽量使用该类型作为事件的委托类型。该委托类型的定义为:
public delegate void EventHandle(object sender, EventArgs e);
其中object类型的参数sender表示引发事件的对象,由于事件成员只能由类型本身(即事件的发布者)触发,因此在触发时传递给该参数的值通常为this。例如:可将 SchoolRing类的OnbellSound 事件定义为 EventHandler 委托类型,那么触发该事件的代码就是“OnbellSound(this,null);”。
2)小demo
/// <summary> /// EventHandler定义 /// </summary> public delegate void EventHandle(object sender,EventArgs e); /// <summary> /// 而SchoolRing的实例在触发OnBellSound事件时,就可以将该类型(RingEventArgs)的对象 /// 作为参数传递给EventHandler委托,下面看激发OnBellSound事件的主要代码 /// </summary> public class SchoolRing//定义发布者类 { public event EventHandle OnbellSound;//委托发布 public void Jow(int ringKind)//打铃方法 { if (ringKind == 1 || ringKind == 2) { Console.Write(ringKind == 1 ? "Class Begin,":"Class Over,"); if (OnbellSound != null)//不等于空,说明它已经订阅了具体的方法 //为了安全,事件只能由类型本身触发(this) OnbellSound(this,new RingEventArgs(ringKind));//回调OnBellSound委托所订阅的具体方法 else Console.WriteLine("ringKind is wrong!"); } } } public class Student//定义订阅者类 { public void SubscribeToRing(SchoolRing schoolRing)//学生订阅铃声这个委托事件 { schoolRing.OnbellSound += SchoolJow; } /// <summary> /// 事件的订阅者可以通过参数来了解哪个是对象触发的事件(这里当然是事件的发布者)。 /// 不过在访问对象时通常要进行强制类型转换。例如,Student类对OnbellSound 事件的处理方法则需修改为: /// </summary> public void SchoolJow(object sender,EventArgs e)//事件的处理方法 { if (((RingEventArgs)e).RingKind == 2)//e强制转换为RingEventArgs类型 { Console.WriteLine("Student rest"); } else if (((RingEventArgs)e).RingKind == 1) { Console.WriteLine("Student learn"); } } public void CancelSubscribe(SchoolRing schoolRing)//取消订阅铃声动作 { schoolRing.OnbellSound -= SchoolJow; } } ///EventHandler 委托的第二个参数e表示事件中包含的数据。如果发布者还要向订阅者传递额外的事件数据, ///那么就需要定义EventArgs类型的派生类。例如,由于需要把打铃参数(1或2)传入到事件中, ///则可以定义如下的RingEventArgs类: public class RingEventArgs : EventArgs { private int ringKind;//描述铃声种类字段 public int RingKind { get { return ringKind; }//获取打铃参数 } public RingEventArgs(int ringKind) { this.ringKind = ringKind;//在构造器中初始化铃声参数 } } class Program { static void Main(string[] args) { SchoolRing sr = new SchoolRing(); Student st = new Student(); st.SubscribeToRing(sr); Console.WriteLine("Please input ringKind(1.Begin Class;2.End Class):"); sr.Jow(Convert.ToInt32(Console.ReadLine())); Console.Read(); } }
5.Windows事件概述
1)概述
事件在Windows这样的图形界面程序中有着极其广泛的应用,事件响应是程序与用户交互的基础。用户的绝大多数操作,如移动鼠标、单机鼠标、选择菜单命令等,都可以触发相关的控件事件。以Button 控件为例,其成员Click就是一个EventHandler类型的事件:
public event EventHandler Click;
用户单击按钮时,Button对象就会调用其保护成员OnClick(它包含了激发Click事件的代码),并通过它来触发Click事件:
protected virtual void onClick(EventArgs e) { if (Click != null) Click(this, e); }
此时,如果在程序中定义了响应事件的处理方法,那么单击按钮就能够执行其中的代码。
2)小demo1
在Form1窗体包含了一个名为Button1的按钮,那么可以在窗体的构造方法中关联事件处理方法,并且在方法代码中执行所需要的功能。代码如下:
public Form1() { InitializeComponent(); button1.Click += new EventHandler(button1_Click); //关联事件处理方法 } private void button1_Click(object sender, EventArgs e) { this.Close(); }
3)小demo2
在Windows窗体中包含了3个按钮控件和一个文本控件,3个按钮的Click事件共用一个事件的处理方法Button。代码如下:
public partial class Form1 : Form { public Form1() { InitializeComponent(); button1.Click += new EventHandler(button_Click); //关联事件处理方法 button2.Click += new EventHandler(button_Click); button3.Click += new EventHandler(button_Click); } private void button_Click(object sender, EventArgs e) //事件处理方法 { textBox1.Text="You pressed" + ((Button)sender).Text; } }
效果图: