event 关键字的来由,为了简化自定义方法的构建来为委托调用列表增加和删除方法。
在编译器处理 event 关键字的时候,它会自动提供注册和注销方法以及任何必要的委托类型成员变量。
这些委托成员变量总是声明为私有的,因此不能直接从触发事件对象访问它们。
温馨提示:如果您对于委托不是很了解,您可以先看 C#委托(Delegate) ,这对您理解本章会有所帮助。
定义一个事件的步骤:
- 需要定义一个委托,它包含事件触发时将要调用方法
- 通过 event 关键字用相关委托声明这个事件
话不多说,我们来看一个示例:
1. 定义Car类:
public class Car { // 这个委托用来与Car事件协作 public delegate void CarEngineHandler(string msg);
// 这种汽车可以发送这些事件 public event CarEngineHandler Exploded; public event CarEngineHandler AboutToBlow; public int CurrentSpeed { get; set; } public int MaxSpeed { get; set; } public string PetName { get; set; } private bool CarIsDead; public Car() { MaxSpeed = 100; } public Car(string name, int maxSp, int currSp) { CurrentSpeed = currSp; MaxSpeed = maxSp; PetName = name; } public void Accelerate(int delta) { // 如果Car无法使用了,触发Exploded事件 if (CarIsDead) { if (Exploded != null) { Exploded("sorry,this car is dead"); } } else { CurrentSpeed += delta; // 确认已无法使用,触发AboutToBlow事件 if ((MaxSpeed - CurrentSpeed) == 10 && AboutToBlow != null) { AboutToBlow("careful buddy ! gonna blow !"); } if (CurrentSpeed >= MaxSpeed) { CarIsDead = true; } else { Console.WriteLine($"CurrentSpeed={CurrentSpeed}"); } } } }
以上我们已经设定了Car对象发送两个自定义事件,这不再需要自定义注册函数,也不需要声明委托成员变量。稍后我们将说到如何使用这个汽车,在此之前,让我们了解一下事件的架构,揭开事件的神秘面纱。
2. 事件神秘面纱
C#事件事实上会扩展两个隐藏的公共方法,一个 add_事件名称,一个 remove_事件名称。
add_Exploded() CIL指令
remove_Exploded() CIL指令
代表事件本身的CIL代码使用 .addon 和 .removeon 指令调用对应的 add_xxx() 和 remove_xxx()方法
3. 使用Car类
了解了这些之后,我们来使用之前定义的Car类:
public class MyEvent { public static void Show() { WriteLine("fun with events"); Car c1 = new Car("bwm", 100, 10); // 注册事件处理程序 c1.AboutToBlow += new Car.CarEngineHandler(CarIsAlomostDoomed); c1.AboutToBlow += new Car.CarEngineHandler(CarAboutToBlow); Car.CarEngineHandler d = new Car.CarEngineHandler(CarExploded); c1.Exploded += d; WriteLine("******Speeding up******"); for (int i = 0; i < 6; i++) { c1.Accelerate(20); } // 注销,从调用列表中移除CarExploded()方法 c1.Exploded -= d; WriteLine("******Speeding up******"); for (int i = 0; i < 6; i++) { c1.Accelerate(20); } } private static void CarExploded(string msg) => WriteLine($"CarExploded-> {msg}"); private static void CarAboutToBlow(string msg) => WriteLine($"CarAboutToBlow=>{msg}"); private static void CarIsAlomostDoomed(string msg) => WriteLine($"CarIsAlomostDoomed-> {msg}"); }
运行效果图:
为了进一步简化事件注册,我们可以用到委托章节学习到的方法组转换语法(解释:我可以在调用以委托作为参数的方法时,直接提供方法的名称,而不是委托对象)
下面请看使用方法组转换,注册和注销事件,粗体部分:
public static void Show() { WriteLine("fun with events"); Car c1 = new Car("bwm", 100, 10); // 注册事件处理程序 c1.AboutToBlow += CarIsAlomostDoomed; c1.AboutToBlow += CarAboutToBlow; c1.Exploded += CarExploded; WriteLine("******Speeding up******"); for (int i = 0; i < 6; i++) { c1.Accelerate(20); }
// 注销,从调用列表中移除CarExploded()方法 c1.Exploded -= CarExploded; WriteLine("******Speeding up******"); for (int i = 0; i < 6; i++) { c1.Accelerate(20); } }
4. 创建自定义事件参数
微软的事件模式:(System.Object sender,System.EventArgs args)这一两个参数的模型。
第一个参数 sender :表示一个对发送事件的对象(Car)的引用,
第二个参数 args :与该事件相关的信息
System.EventArgs 基类源代码:
public class EventArgs { public static readonly EventArgs Empty = new EventArgs(); public EventArgs() { } }
那么对于简单的事件类型来说,我们可以直接传递一个EventArgs的实例,但是如果我们期望传递自定义的数据,就应该从System.EventArgs派生出一个子类。
我们接下来就为我们的 Car 自定义一个符合这种事件模式的事件参数,新建一个 CarEventArgs 类,包含一个字符串,表示要发送给接收者的信息:
public class CarEventArgs : EventArgs { public readonly string msg; public CarEventArgs(string message) { msg = message; } }
我们修改一下Car类,新添加一个 CarCustomEngineHandler 委托,并且更改相应的事件代码:
public class Car { public delegate void CarCustomEngineHandler(object sender, CarEventArgs e); // 模仿微软正规(object sender, EventArgs e)写法 public event CarCustomEngineHandler CustomExploded; public event CarCustomEngineHandler CustomAboutToBlow; public void AccelerateCustom(int delta) { if (CarIsDead) { if (CustomExploded != null) { CustomExploded(this, new CarEventArgs("sorry,this car is dead")); } } else { CurrentSpeed += delta; if ((MaxSpeed - CurrentSpeed) == 10 && CustomAboutToBlow != null) { CustomAboutToBlow(this, new CarEventArgs("careful buddy ! gonna blow !")); } if (CurrentSpeed >= MaxSpeed) { CarIsDead = true; } else { Console.WriteLine($"CurrentSpeed={CurrentSpeed}"); } } } }
看一下调用粗体部分(是如何使用传递的参数sender,e的):
public class MyCustomEvents { public static void Show() { WriteLine("fun with events"); Car c1 = new Car("bwm", 100, 10); c1.CustomAboutToBlow += CarIsAlomostDoomed; c1.CustomAboutToBlow += CarAboutToBlow; Car.CarCustomEngineHandler d = CarExploded; c1.CustomExploded += d; WriteLine("******Speeding up******"); for (int i = 0; i < 6; i++) { c1.AccelerateCustom(20); } c1.CustomExploded -= d; WriteLine("******Speeding up******"); for (int i = 0; i < 6; i++) { c1.AccelerateCustom(20); } } private static void CarExploded(object sender, CarEventArgs e) => WriteLine($"CarExploded->{((Car)sender)?.PetName} {e.msg}"); private static void CarAboutToBlow(object sender, CarEventArgs e) => WriteLine($"CarAboutToBlow=>{((Car)sender)?.PetName} {e.msg}"); private static void CarIsAlomostDoomed(object sender, CarEventArgs e) => WriteLine($"CarIsAlomostDoomed->{((Car)sender)?.PetName} {e.msg}"); }
5. 泛型 EventHandler<T> 委托
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
由于很多自定义委托接受(object,EventArgs)这样的参数结构,那么我们可以使用框架内置的 EventHandler<> 来简化我们的事件 委托。
首先修改一下Car类:
public class Car { public event EventHandler<CarEventArgs> StandardExploded; public event EventHandler<CarEventArgs> StandardAboutToBlow; public void AccelerateStandard(int delta) { if (CarIsDead) { if (StandardExploded != null) { StandardExploded(this, new CarEventArgs("sorry,this car is dead")); } } else { CurrentSpeed += delta; if ((MaxSpeed - CurrentSpeed) == 10 && StandardAboutToBlow != null) { StandardAboutToBlow(this, new CarEventArgs("careful buddy ! gonna blow !")); } if (CurrentSpeed >= MaxSpeed) { CarIsDead = true; } else { Console.WriteLine($"CurrentSpeed={CurrentSpeed}"); } } } }
调用代码其实和上一段并没有太大差异,这里还是贴出来:
public class MyStandardEvent { public static void Show() { WriteLine("fun with events"); Car c1 = new Car("bwm", 100, 10); c1.StandardAboutToBlow += CarIsAlomostDoomed; c1.StandardAboutToBlow += CarAboutToBlow; EventHandler<CarEventArgs> d = CarExploded; c1.StandardExploded += d; WriteLine("******Speeding up******"); for (int i = 0; i < 6; i++) { c1.AccelerateStandard(20); } c1.StandardExploded -= d; WriteLine("******Speeding up******"); for (int i = 0; i < 6; i++) { c1.AccelerateStandard(20); } } private static void CarExploded(object sender, CarEventArgs e) => WriteLine($"CarExploded->{((Car)sender)?.PetName} {e.msg}"); private static void CarAboutToBlow(object sender, CarEventArgs e) => WriteLine($"CarAboutToBlow=>{((Car)sender)?.PetName} {e.msg}"); private static void CarIsAlomostDoomed(object sender, CarEventArgs e) => WriteLine($"CarIsAlomostDoomed->{((Car)sender)?.PetName} {e.msg}"); }
6.匿名方法
这么简单的处理操作, CarExploded() ,CarAboutToBlow()这一的方法很少会被调用委托之外的任何程序所调用。从生成效率来说,手工定义一个由委托对象调用的方法有点麻烦耶。
为了解决这种情况,现在事件注册时,可以直接将一个委托与一段代码关联 -- 匿名方法。
我们修改一下调用Car类的地方(注意粗体部分、最后一个大括号 ";" 结束):
public class MyAnonymousMtehoden { public static void Show() { int aboutToBlowCounter = 0; WriteLine("fun with events"); Car c1 = new Car("bwm", 100, 10); c1.StandardAboutToBlow += delegate { WriteLine("Eek,going to fast"); }; c1.StandardAboutToBlow += delegate (object sender, CarEventArgs e) { aboutToBlowCounter++; WriteLine($"CarAboutToBlow=>{((Car)sender)?.PetName} {e.msg}"); }; c1.StandardExploded += delegate (object sender, CarEventArgs e) { aboutToBlowCounter++; WriteLine($"Exploded=>{((Car)sender)?.PetName} {e.msg}"); }; for (int i = 0; i < 6; i++) { c1.AccelerateStandard(20); } WriteLine($"aboutToBlowCounter={aboutToBlowCounter}"); } }
本文参考《精通C#》
学无止境,望各位看官多多指教。