委托基础详解
2015-04-10 11:50 糯米粥 阅读(466) 评论(0) 编辑 收藏 举报 /*委托是一个类型安全的对象
* 它指向程序中另一个以后被调用的方法或多个方法。委托类型包含3个重要的信息:
* 1:它所调用的方法的名称
* 2:该方法的参数(可选)
* 3:该方法的返回值(可选)
* 注:.net委托既可以指向静态方法,也可以指向实例方法
*
* 当一个委托对象被创建并提供了上述信息后,它可以在运行时动态调用其他指向的方法,可以看到 .Net Framework 中每个委托(包含自定义委托)
* 都被自动赋予同步或异步访问方法的能力,可以不用手工创建与管理一个Thread对象而直接调用另一个辅助线程上的方法。大大简化了编程工作
*/
一个简单的委托事例
委托的定义用delegate关键字
//这个委托可以指向任何传入两个整数返回一个整数的方法 public delegate int BinaryOp(int x, int y);
定义一个类
public class SimpleMath { //这个类包含了BinaryOp将指向的方法 public static int Add(int x, int y) { return x + y; } }
调用
#region 一个简单的委托事例 /*一个简单的委托事例*/ //创建一个指向SimpleMath.Add()方法的BinaryOp对象 BinaryOp b = new BinaryOp(SimpleMath.Add); /*使用委托对象间接调用Add()方法 底层:Invoke()在这里被调用了,可以:b.Invoke(10,10),不过我们不需要显示的调用Invoke(); * 在底层,运行库实际上在MulticastDelegate派生类上调用了编译器生成的Invoke()方法 */ Console.WriteLine("10+10 is {0}", b(10, 10)); //20 #endregion
/*当C#编译处理委托类型时,它先自动生成一个派生自 System.MulticastDelegate的密封类(BinaryOp)如图
* 这个类与它的基类System.Delegate一起为委托提供必要的基础设施,以维护(委托指向的方法)
* 以后将要调用方法的列表。
* 我们生成项目后。把.exe文件拖到Reflector中
*
*
* 可以看到生成后,Program类定义了3个公共方法
* 1:Invoke()是核心方法,因为他被用来以同步方式调用委托对象维护(委托指向的方法)的每个方法
* 这里的同步就是指被调用者必须等待调用完成才 能继续执行
* 2:BeginInvoke()和EndInvoke()方法能在第二个执行线程上异步调用当前方法
*
* 那么编译器又是如何确切知道怎样定义Invoke()、BeginInvoke()和EndInvoke()方法的呢?
* 把图中的BinaryOp分解如下:sealed代表密封
* sealed class BinaryOp:System.MulticastDelegate
* {
* public int Invoke(int x,int y);
* public IAsyncResult BeginInvoke(int x,int y,AsyncCallback callback,object state);
* public int EndInvoke(IAsyncResult result);
* }
* 解:
* Invoke()方法定义的参数和返回值完全匹配 BinaryOp 委托的定义。
* BeginInvoke()方法的参数(这里是两个整数)也基于 BinaryOp 委托,但BeginInvoke总是提供最后两个参数(AsyncCallback类型与object类型)用于
* 异步方法调用。
* 最后EndInvoke()方法的返回值与初始化委托 BinaryOp 声明相同,总是以一个实现了IAsyncResult接口的对象作为唯一的参数。
*
*
* 同理:public delegate string MyDelegate(bool a,bool b,bool c)
* 那么:
* sealed class MyDelegate:System.MulticastDelegate
* {
* public string Invoke(bool a,bool b,bool c);
* public IAsyncResult BeginInvoke(bool a,bool b,bool c,AsyncCallback callback,object state);
* public string EndInvoke(IAsyncResult result);
* }
*
* 委托还可以指向包含任意数量out 或 ref参数(已经params关键字标记的数组参数)的方法
*
* 同理:public delegate string MyOtherDelegate(out bool a,ref bool b,int c)
* 那么:Invoke()和BeginInvoke()方法都没变。但EndInvoke()方法有所变化,其中包括委托类型定义的所有out/ref参数
* sealed class MyOtherDelegate:System.MulticastDelegate
* {
* public string Invoke(out bool a,ref bool b,int c);
* public IAsyncResult BeginInvoke(out bool a,ref bool b,int c,AsyncCallback callback,object state);
* public string EndInvoke(out bool a,ref bool b,IAsyncResult result);
* }
*
* 故:C#委托类型的定义会生成一个密封类,它包含3个编译器生成的方法,这3个方法的参数与返回值基于委托的声明。
*
* System.MulticastDelegate和System.Delegate基类
*
* 使用C#中delegate关键字创建委托的时候,也间接的声明了一个派生自System.MulticastDelegate的类,这个类使其继承类可以访问包含由委托对象
* 维护的方法地址的列表已经一些处理调用列表的附加方法(与少数重载的操作符)
*
*
*
*/
使用委托发送对象状态通知
/*
*1:定义将通知发送给调用者的委托类型
* 2:声明Car类中每个委托类型的成员变量
* 3:在Car上创建辅助函数使调用者能指定由委托成员变量保存的方法
* 4:修改Accelerate()方法在适当的情形下调用委托的调用列表
*/
class Car { //1:定义委托类型 public delegate void CarEngineHandler(string msgForCaller); //2:定义每个委托类型的成员变量 private CarEngineHandler listOfHandlers; //3:向调用者添加注册函数,自定义的注册方法封装的私有委托成员变量 public void RegisterWithCarEngine(CarEngineHandler methodToCall) { listOfHandlers = methodToCall; #region 多路广播 //委托支持多路广播,即一个委托对象可以维护一个可调用方法的列表而不只是单独一个方法,给一个委托对象添加多个方法时,不用直接分配(赋值操作符 = ),重载+=操作符即可 //listOfHandlers += methodToCall; (底层是使用的Delegate.Combine()方法) //注:既然+=操作符 是添加方法 那么-=操作符则是从委托调用列表中移除成员 (底层是使用的Delegate.Remove()方法) #endregion } /* 注:将委托定义在使用它的类型作用域里是很普遍的 * 严格来说,可以将委托成员变量定义为公共的,这样就不需要创建额外的注册方法,然而,如果将委托成员定义为私有的。我们就强制了封装服务并提供了更加类型安全的解决方案。 * 比如:C# event关键字,声明为事件 */ //内部状态数据 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; } //此方法:使Car对象向订阅者发送引擎相关的信息。 public void Accelerate(int delta) { //当前汽车是否不能用 if (carIsDead) { //判断当前是否有订阅者 if (listOfHandlers != null) { listOfHandlers("您的汽车已经报废"); } } else { CurrentSpeed += delta; //假设。距离最大速度剩下10 则发送警报信息 if (10 == (MaxSpeed - CurrentSpeed) && listOfHandlers != null) { listOfHandlers("不能在加速了,滴滴哒哒"); } if (CurrentSpeed >= MaxSpeed) carIsDead = true; else Console.WriteLine("当前速度 = {0}", CurrentSpeed); } } }
测试代码
//使用委托发送对象状态通知 #region 使用委托发送对象状态通知 /* Console.WriteLine("小汽车上路。。。。\n"); //首先:创建一个Car对象 Car c1 = new Car("BMW", 100, 10); //现在:告诉汽车,它想要想我们发送信息时调用哪个方法 c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent)); //更简单的写法(方法组转换语法),即 在调用以委托作为参数的方法时,直接提供方法的名称,而不用创建委托对象 c1.RegisterWithCarEngine(OnCarEngineEvent); //然后:加速,将会触发事件 Console.WriteLine("**加速***"); for (int i = 0; i < 6; i++) { c1.Accelerate(20); } */ #endregion
要传入事件的函数
//要传入的事件方法 public static void OnCarEngineEvent(string msg) { Console.WriteLine("\n***报警信息*****"); Console.WriteLine("信息提示 => {0}", msg); Console.WriteLine("\n********"); }
委托协变
//委托协变,你有没有想过,如果委托类型指向返回自定义类类型的方法?
/*
* 说白了协变和逆变也就是,创建一个委托,指向多个方法,方法的参数是存在继承关系的对象,之间的一个转换
*/
定义两个类。必须有继承关系
class China : Person { public void show() { if (tag == null) Console.WriteLine("派生类没有注册"); else Console.WriteLine("派生类已经注册"); } } class Person { public delegate Person DelegateHander(); protected DelegateHander tag; public void Register(DelegateHander meth) { tag = meth; } public void show() { if (tag == null) Console.WriteLine("父类没有注册"); else Console.WriteLine("父类已经注册"); } }
定义与委托签名匹配的函数
public static Person GetPer() { return new Person(); } public static China GetChi() { return new China(); }
测试代码
#region 委托协变 //委托协变,你有没有想过,如果委托类型指向返回自定义类类型的方法? /* * 说白了协变和逆变也就是,创建一个委托,指向多个方法,方法的参数是存在继承关系的对象,之间的一个转换 */ /*Person tag = new Person(); tag.Register(GetPer); Person p = tag; tag.show(); Person ct = new China(); //协变允许这种目标对象赋值, ct.Register(GetChi); //委托本来是返回Person类型的。但因为协变,做了一个显示强制类型转换,这里得到派生类 类型的方法, China c = (China)ct; c.show(); */ #endregion
泛型委托
如果你希望定义一个委托类型来调用任何返回void并且接受单个参数的方法,但这个参数类型可能会不同,那么,就可以通过类型参数来构建 泛型委托
泛型委托的定义:
//这个泛型委托可以调用任何返回void并接受单个参数的方法 public delegate void myGenericDelegate<T>(T tag);
myGenericDelegate<T>定义了一个类型参数表示要传入委托目标的实参,在创建类型实例时,需要指定类型参数的值以及委托调用的方法的名称。如:
//如果使用字符串作为类型参数
//myGenericDelegate<string> strTarget = new myGenericDelegate<string>(StringTarget);
//strTarget("字符串类型参数");
static void StringTarget(string tag) { Console.WriteLine("传来的字符串是 {0}", tag); } #region 泛型委托 //泛型委托 /* 疑问:如果你希望定义一个委托类型来调用任何返回void并且接受单个参数的方法,但这个参数类型可能会不同,那么,就可以通过类型参数来构建 泛型委托 * * myGenericDelegate<T>定义了一个类型参数表示要传入委托目标的实参,在创建类型实例时,需要指定类型参数的值以及委托调用的方法的名称。如: */ //如果使用字符串作为类型参数 //myGenericDelegate<string> strTarget = new myGenericDelegate<string>(StringTarget); //strTarget("字符串类型参数"); #endregion
C# 事件
//C# 事件
/*
从上面使用的委托来看,使用委托会有一些重复的代码,当定义委托,需要声明必要的成员变量已经创建自定义的注册/注销方法来保护封装。
* 如果没有把委托定义为私有的,那么调用者就可以直接访问委托对象,这样调用者就可以把变量重新赋值为新的委托对象,实际上也就删除了当前要调用的方法列表。
* 也就是说,调用者可以直接调用委托列表
*/
定义一个Animal类
class Animal { /* 定义一个事件分为两个步骤: * 1:需要定义一个委托类型,它包含在事件触发时将要调用的方法 * 2:通过Event关键字用相关委托声明这个事件 */ //这个委托用来与Animal的事件协作 public delegate void AnimalEngineHandler(string name);//声明事件 public event AnimalEngineHandler Call; //提示方法 public void showMsg(string name) { if (Call != null) { if (name == "熊猫") Call("杀了它你就是国宝!"); else Call("^-^"); } } }
编写测试代码
#region C# 事件 //C# 事件 /* 从上面使用的委托来看,使用委托会有一些重复的代码,当定义委托,需要声明必要的成员变量已经创建自定义的注册/注销方法来保护封装。 * 如果没有把委托定义为私有的,那么调用者就可以直接访问委托对象,这样调用者就可以把变量重新赋值为新的委托对象,实际上也就删除了当前要调用的方法列表。 * 也就是说,调用者可以直接调用委托列表 */ //传入监听的事件,调用者只需要使用+=和-=操作符即可 //Animal.AnimalEngineHandler c = new Animal.AnimalEngineHandler(AnimalMsg); //Animal a = new Animal(); //a.Call += c; //a.showMsg("dd"); Animal an = new Animal(); //an.Call += new Animal.AnimalEngineHandler(AnimalMsg); //更简单的方法,利用方法组转换 //an.Call += AnimalMsg; //an.showMsg("熊猫"); #endregion
static void AnimalMsg(string msg)
{
Console.WriteLine(msg);
}
创建自定义的事件参数
/*
微软推荐的事件模式:第一个参数是一个 System.Object,第二个参数是 System.EventArgs的子类型
* System.Object:表示一个对发送事件对象(例如上面的Car对象)的引用,第二个参数则表示与该事件相关的信息
* System.EventArgs基类:表示一个不发送任何自定义信息的事件
*
* 对于简单的事件来说,我们可以直接传递一个EventArgs的实例,但如果需要传递自定义数据,应该构建一个派生自EventArgs的类,
* 假定:有一个AnimalEventArgs的类,它保存一个字符串,表示要发送给接收者的信息
*/
创建一个AnimalEventArgs。派生自EventArgs
//派生自EventArgs的类,它保存一个字符串,表示要发送给接收者的信息 class AnimalEventArgs : EventArgs { public readonly string msg; public AnimalEventArgs(string message) { msg = message; } }
我们直接在上面的Animal类中修改。为了不改动之前的代码。直接在Animal类中添加相应的委托,事件和方法
//修改委托:以符合微软推荐的事件模式 public delegate void AnimalEngineHandler1(object sender, AnimalEventArgs e); //修改 public event AnimalEngineHandler1 Call1; //提示方法 public void showMsg1(string name) { if (Call1 != null) { if (name == "大熊猫") Call1(this, new AnimalEventArgs("杀了它你就是国宝")); else Call1(this, new AnimalEventArgs("^-^")); } }
最后编写测试代码
#region 创建自定义的事件参数 //创建自定义的事件参数 /* 微软推荐的事件模式:第一个参数是一个 System.Object,第二个参数是 System.EventArgs的子类型 * System.Object:表示一个对发送事件对象(例如上面的Car对象)的引用,第二个参数则表示与该事件相关的信息 * System.EventArgs基类:表示一个不发送任何自定义信息的事件 * * 对于简单的事件来说,我们可以直接传递一个EventArgs的实例,但如果需要传递自定义数据,应该构建一个派生自EventArgs的类, * 假定:有一个AnimalEventArgs的类,它保存一个字符串,表示要发送给接收者的信息 */ //Animal a = new Animal(); //a.Call1 += AnimalMsg1; //a.showMsg1("大熊猫"); #endregion
泛型EventHandler<T>委托
由于很多自定义委托接受object作为第一个参数,EventArgs派生类型作为第二个参数,我们可以通过使用泛型EventHandler<T>类型来进一步简化之前的示列
其中T就是自定义EventArgs类型,
同样,在Animal中新增下面测试代码
//使用泛型EventHandler<T>委托,就不在需要定义一个自定义委托类型 public event EventHandler<AnimalEventArgs> eventHandlerDemo; //提示方法 public void showMsg3(string name) { if (eventHandlerDemo != null) { if (name == "大熊猫") eventHandlerDemo(this, new AnimalEventArgs("杀了它你就是国宝")); else eventHandlerDemo(this, new AnimalEventArgs("^-^")); } }
编写测试代码
#region 泛型EventHandler<T>委托 //泛型EventHandler<T>委托 /* 由于很多自定义委托接受object作为第一个参数,EventArgs派生类型作为第二个参数,我们可以通过使用泛型EventHandler<T>类型来进一步简化之前的示列 * 其中T就是自定义EventArgs类型, */ Animal a3 = new Animal(); //EventHandler<AnimalEventArgs> a5 = new EventHandler<AnimalEventArgs>(AnimalMsg1); //a3.eventHandlerDemo += a5; //简写 //a3.eventHandlerDemo += AnimalMsg1; //a3.showMsg3("大熊猫"); #endregion
C# 匿名方法
#region C# 匿名方法 //C# 匿名方法 /* * 可以看到,当一个调用者想监听传进来的事件时,它必须定义一个唯一的与相关联委托签名匹配的方法 * 这样的方法,很少会被调用委托之外的任何程序所调用,从生产效的角度来说,手工定义一个由委托对象调用的方法显得有点繁琐,不会很受欢迎。 * 那么。可以在事件注册时,直接将一个委托与一段代码相关联,这种代码的正式名称为匿名方法(即没有方法名称) */ //匿名方法中最后一个大括号必须以分号结束,否则,将产生一个编译错误 //a3.eventHandlerDemo += delegate(object sender, AnimalEventArgs e) //{ // Console.WriteLine(e.msg); //}; //int c = 90; //匿名方法的外部变量==》定义匿名方法的本地变量,称为匿名方法的外部变量 //如果不需要接收由事件发送的传入参数,则 //a3.eventHandlerDemo += delegate //{ // Console.WriteLine("不需要接收由事件发送的传入参数"); // /* // 匿名方法注意事项 // * 1:匿名方法不能访问定义方法中的ref或out参数 // * 2:匿名方法中的本地变量不能与外部方法中的本地变量重名 // * 3:匿名方法可以访问外部类作用域中的实例变量或静态变量 // * 4:匿名方法内的本地变量可以与外部类的成员变量同名,因为本地变量的作用域不同,可以隐藏外部类的成员变量 // */ // //int c = 8;//匿名方法中的本地变量不能与外部方法中的本地变量重名 // Console.WriteLine(c); //访问匿名方法的外部变量 //}; //a3.showMsg3("大熊猫"); #endregion
Lambda 表达式
//Lambda 表达式 /* * 我们知道C#支持内联处理方法,通过直接把一段代码语句赋值给事件,即:匿名方法,Lambda表达式可以用更简单的方式来写匿名方法,彻底简化了对.net委托类型的使用 */ //如果没有参数的委托交互,可以使用空括号表示表达式的参数列表 a3.eventHandlerDemo += (sender, e) => { Console.WriteLine(e.msg); }; a3.showMsg3("大熊猫");
我是在vs中边测试边记录的。每个模块都用一个 region 区域别包含了。你可以把所有region都注释。然后从上面取消一个一个的 region 里面的代码来注释,看效果
下面是全部源码,你可以复制到自己的vs中
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace DeleagateDemo 7 { 8 /*委托是一个类型安全的对象 9 * 它指向程序中另一个以后被调用的方法或多个方法。委托类型包含3个重要的信息: 10 * 1:它所调用的方法的名称 11 * 2:该方法的参数(可选) 12 * 3:该方法的返回值(可选) 13 * 注:.net委托既可以指向静态方法,也可以指向实例方法 14 * 15 * 当一个委托对象被创建并提供了上述信息后,它可以在运行时动态调用其他指向的方法,可以看到 .Net Framework 中每个委托(包含自定义委托) 16 * 都被自动赋予同步或异步访问方法的能力,可以不用手工创建与管理一个Thread对象而直接调用另一个辅助线程上的方法。大大简化了编程工作 17 */ 18 class Program 19 { 20 //这个泛型委托可以调用任何返回void并接受单个参数的方法 21 public delegate void myGenericDelegate<T>(T tag); 22 23 24 //这个委托可以指向任何传入两个整数返回一个整数的方法 25 public delegate int BinaryOp(int x, int y); 26 27 static void Main(string[] args) 28 { 29 30 /*当C#编译处理委托类型时,它先自动生成一个派生自 System.MulticastDelegate的密封类(BinaryOp)如图 31 * 这个类与它的基类System.Delegate一起为委托提供必要的基础设施,以维护(委托指向的方法) 32 * 以后将要调用方法的列表。 33 * 我们生成项目后。把.exe文件拖到Reflector中 34 * 35 * 36 * 可以看到生成后,Program类定义了3个公共方法 37 * 1:Invoke()是核心方法,因为他被用来以同步方式调用委托对象维护(委托指向的方法)的每个方法 38 * 这里的同步就是指被调用者必须等待调用完成才 能继续执行 39 * 2:BeginInvoke()和EndInvoke()方法能在第二个执行线程上异步调用当前方法 40 * 41 * 那么编译器又是如何确切知道怎样定义Invoke()、BeginInvoke()和EndInvoke()方法的呢? 42 * 把图中的BinaryOp分解如下:sealed代表密封 43 * sealed class BinaryOp:System.MulticastDelegate 44 * { 45 * public int Invoke(int x,int y); 46 * public IAsyncResult BeginInvoke(int x,int y,AsyncCallback callback,object state); 47 * public int EndInvoke(IAsyncResult result); 48 * } 49 * 解: 50 * Invoke()方法定义的参数和返回值完全匹配 BinaryOp 委托的定义。 51 * BeginInvoke()方法的参数(这里是两个整数)也基于 BinaryOp 委托,但BeginInvoke总是提供最后两个参数(AsyncCallback类型与object类型)用于 52 * 异步方法调用。 53 * 最后EndInvoke()方法的返回值与初始化委托 BinaryOp 声明相同,总是以一个实现了IAsyncResult接口的对象作为唯一的参数。 54 * 55 * 56 * 同理:public delegate string MyDelegate(bool a,bool b,bool c) 57 * 那么: 58 * sealed class MyDelegate:System.MulticastDelegate 59 * { 60 * public string Invoke(bool a,bool b,bool c); 61 * public IAsyncResult BeginInvoke(bool a,bool b,bool c,AsyncCallback callback,object state); 62 * public string EndInvoke(IAsyncResult result); 63 * } 64 * 65 * 委托还可以指向包含任意数量out 或 ref参数(已经params关键字标记的数组参数)的方法 66 * 67 * 同理:public delegate string MyOtherDelegate(out bool a,ref bool b,int c) 68 * 那么:Invoke()和BeginInvoke()方法都没变。但EndInvoke()方法有所变化,其中包括委托类型定义的所有out/ref参数 69 * sealed class MyOtherDelegate:System.MulticastDelegate 70 * { 71 * public string Invoke(out bool a,ref bool b,int c); 72 * public IAsyncResult BeginInvoke(out bool a,ref bool b,int c,AsyncCallback callback,object state); 73 * public string EndInvoke(out bool a,ref bool b,IAsyncResult result); 74 * } 75 * 76 * 故:C#委托类型的定义会生成一个密封类,它包含3个编译器生成的方法,这3个方法的参数与返回值基于委托的声明。 77 * 78 * System.MulticastDelegate和System.Delegate基类 79 * 80 * 使用C#中delegate关键字创建委托的时候,也间接的声明了一个派生自System.MulticastDelegate的类,这个类使其继承类可以访问包含由委托对象 81 * 维护的方法地址的列表已经一些处理调用列表的附加方法(与少数重载的操作符) 82 * 83 * 84 * 85 */ 86 87 #region 一个简单的委托事例 88 /*一个简单的委托事例*/ 89 90 //创建一个指向SimpleMath.Add()方法的BinaryOp对象 91 //BinaryOp b = new BinaryOp(SimpleMath.Add); 92 93 /*使用委托对象间接调用Add()方法 底层:Invoke()在这里被调用了,可以:b.Invoke(10,10),不过我们不需要显示的调用Invoke(); 94 * 在底层,运行库实际上在MulticastDelegate派生类上调用了编译器生成的Invoke()方法 95 */ 96 //Console.WriteLine("10+10 is {0}", b(10, 10)); //20 97 #endregion 98 99 100 //使用委托发送对象状态通知 101 #region 使用委托发送对象状态通知 102 /* 103 Console.WriteLine("小汽车上路。。。。\n"); 104 105 //首先:创建一个Car对象 106 Car c1 = new Car("BMW", 100, 10); 107 108 //现在:告诉汽车,它想要想我们发送信息时调用哪个方法 109 c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent)); 110 111 //更简单的写法(方法组转换语法),即 在调用以委托作为参数的方法时,直接提供方法的名称,而不用创建委托对象 112 c1.RegisterWithCarEngine(OnCarEngineEvent); 113 114 //然后:加速,将会触发事件 115 Console.WriteLine("**加速***"); 116 for (int i = 0; i < 6; i++) 117 { 118 c1.Accelerate(20); 119 } 120 */ 121 #endregion 122 123 #region 委托协变 124 //委托协变,你有没有想过,如果委托类型指向返回自定义类类型的方法? 125 /* 126 * 说白了协变和逆变也就是,创建一个委托,指向多个方法,方法的参数是存在继承关系的对象,之间的一个转换 127 128 */ 129 /*Person tag = new Person(); 130 tag.Register(GetPer); 131 132 Person p = tag; 133 tag.show(); 134 135 Person ct = new China(); 136 //协变允许这种目标对象赋值, 137 ct.Register(GetChi); 138 //委托本来是返回Person类型的。但因为协变,做了一个显示强制类型转换,这里得到派生类 类型的方法, 139 China c = (China)ct; 140 c.show(); 141 */ 142 143 #endregion 144 145 146 147 #region 泛型委托 148 //泛型委托 149 /* 150 疑问:如果你希望定义一个委托类型来调用任何返回void并且接受单个参数的方法,但这个参数类型可能会不同,那么,就可以通过类型参数来构建 泛型委托 151 * 152 * myGenericDelegate<T>定义了一个类型参数表示要传入委托目标的实参,在创建类型实例时,需要指定类型参数的值以及委托调用的方法的名称。如: 153 */ 154 //如果使用字符串作为类型参数 155 //myGenericDelegate<string> strTarget = new myGenericDelegate<string>(StringTarget); 156 //strTarget("字符串类型参数"); 157 #endregion 158 159 #region C# 事件 160 //C# 事件 161 /* 162 从上面使用的委托来看,使用委托会有一些重复的代码,当定义委托,需要声明必要的成员变量已经创建自定义的注册/注销方法来保护封装。 163 * 如果没有把委托定义为私有的,那么调用者就可以直接访问委托对象,这样调用者就可以把变量重新赋值为新的委托对象,实际上也就删除了当前要调用的方法列表。 164 * 也就是说,调用者可以直接调用委托列表 165 */ 166 //传入监听的事件,调用者只需要使用+=和-=操作符即可 167 //Animal.AnimalEngineHandler c = new Animal.AnimalEngineHandler(AnimalMsg); 168 //Animal a = new Animal(); 169 //a.Call += c; 170 //a.showMsg("dd"); 171 172 Animal an = new Animal(); 173 //an.Call += new Animal.AnimalEngineHandler(AnimalMsg); 174 //更简单的方法,利用方法组转换 175 //an.Call += AnimalMsg; 176 //an.showMsg("熊猫"); 177 #endregion 178 179 #region 创建自定义的事件参数 180 //创建自定义的事件参数 181 /* 182 微软推荐的事件模式:第一个参数是一个 System.Object,第二个参数是 System.EventArgs的子类型 183 * System.Object:表示一个对发送事件对象(例如上面的Car对象)的引用,第二个参数则表示与该事件相关的信息 184 * System.EventArgs基类:表示一个不发送任何自定义信息的事件 185 * 186 * 对于简单的事件来说,我们可以直接传递一个EventArgs的实例,但如果需要传递自定义数据,应该构建一个派生自EventArgs的类, 187 * 假定:有一个AnimalEventArgs的类,它保存一个字符串,表示要发送给接收者的信息 188 */ 189 //Animal a = new Animal(); 190 //a.Call1 += AnimalMsg1; 191 //a.showMsg1("大熊猫"); 192 #endregion 193 194 #region 泛型EventHandler<T>委托 195 //泛型EventHandler<T>委托 196 /* 197 由于很多自定义委托接受object作为第一个参数,EventArgs派生类型作为第二个参数,我们可以通过使用泛型EventHandler<T>类型来进一步简化之前的示列 198 * 其中T就是自定义EventArgs类型, 199 */ 200 Animal a3 = new Animal(); 201 //EventHandler<AnimalEventArgs> a5 = new EventHandler<AnimalEventArgs>(AnimalMsg1); 202 //a3.eventHandlerDemo += a5; 203 204 //简写 205 //a3.eventHandlerDemo += AnimalMsg1; 206 207 //a3.showMsg3("大熊猫"); 208 #endregion 209 210 #region C# 匿名方法 211 //C# 匿名方法 212 /* 213 * 可以看到,当一个调用者想监听传进来的事件时,它必须定义一个唯一的与相关联委托签名匹配的方法 214 * 这样的方法,很少会被调用委托之外的任何程序所调用,从生产效的角度来说,手工定义一个由委托对象调用的方法显得有点繁琐,不会很受欢迎。 215 * 那么。可以在事件注册时,直接将一个委托与一段代码相关联,这种代码的正式名称为匿名方法(即没有方法名称) 216 */ 217 //匿名方法中最后一个大括号必须以分号结束,否则,将产生一个编译错误 218 //a3.eventHandlerDemo += delegate(object sender, AnimalEventArgs e) 219 //{ 220 // Console.WriteLine(e.msg); 221 //}; 222 223 //int c = 90; //匿名方法的外部变量==》定义匿名方法的本地变量,称为匿名方法的外部变量 224 225 //如果不需要接收由事件发送的传入参数,则 226 //a3.eventHandlerDemo += delegate 227 //{ 228 // Console.WriteLine("不需要接收由事件发送的传入参数"); 229 230 // /* 231 // 匿名方法注意事项 232 // * 1:匿名方法不能访问定义方法中的ref或out参数 233 // * 2:匿名方法中的本地变量不能与外部方法中的本地变量重名 234 // * 3:匿名方法可以访问外部类作用域中的实例变量或静态变量 235 // * 4:匿名方法内的本地变量可以与外部类的成员变量同名,因为本地变量的作用域不同,可以隐藏外部类的成员变量 236 // */ 237 // //int c = 8;//匿名方法中的本地变量不能与外部方法中的本地变量重名 238 // Console.WriteLine(c); //访问匿名方法的外部变量 239 //}; 240 //a3.showMsg3("大熊猫"); 241 #endregion 242 243 244 //Lambda 表达式 245 /* 246 * 我们知道C#支持内联处理方法,通过直接把一段代码语句赋值给事件,即:匿名方法,Lambda表达式可以用更简单的方式来写匿名方法,彻底简化了对.net委托类型的使用 247 */ 248 //如果没有参数的委托交互,可以使用空括号表示表达式的参数列表 249 a3.eventHandlerDemo += (sender, e) => { 250 Console.WriteLine(e.msg); 251 }; 252 a3.showMsg3("大熊猫"); 253 Console.ReadLine(); 254 255 256 257 } 258 259 public static Person GetPer() 260 { 261 return new Person(); 262 } 263 public static China GetChi() 264 { 265 return new China(); 266 } 267 //要传入的事件方法 268 public static void OnCarEngineEvent(string msg) 269 { 270 Console.WriteLine("\n***报警信息*****"); 271 Console.WriteLine("信息提示 => {0}", msg); 272 Console.WriteLine("\n********"); 273 } 274 275 static void StringTarget(string tag) 276 { 277 Console.WriteLine("传来的字符串是 {0}", tag); 278 } 279 static void IntTarget(int tag) 280 { 281 Console.WriteLine("传来的整数是 {0}", tag); 282 } 283 static void AnimalMsg(string msg) 284 { 285 Console.WriteLine(msg); 286 } 287 /// <summary> 288 /// 289 /// </summary> 290 /// <param name="sender">发送事件对象的引用</param> 291 /// <param name="e">表示与该事件相关的信息</param> 292 static void AnimalMsg1(object sender, AnimalEventArgs e) 293 { 294 /*如果接收者想与发送事件的对象交互,我们可以显示强制类型转换System.Object, 295 * 这样就可以使用传递给事件通知对象中的任何公共成员 296 */ 297 //为了安全起见,这里可以强制转换前做一次运行时检查 298 if (sender is Animal) 299 { 300 Animal a = sender as Animal; 301 Console.WriteLine("name is {0},message is {1}", a.n, e.msg); 302 } 303 } 304 } 305 public class SimpleMath 306 { 307 //这个类包含了BinaryOp将指向的方法 308 public static int Add(int x, int y) 309 { 310 return x + y; 311 } 312 } 313 314 /* 315 *1:定义将通知发送给调用者的委托类型 316 * 2:声明Car类中每个委托类型的成员变量 317 * 3:在Car上创建辅助函数使调用者能指定由委托成员变量保存的方法 318 * 4:修改Accelerate()方法在适当的情形下调用委托的调用列表 319 */ 320 class Car 321 { 322 323 //1:定义委托类型 324 public delegate void CarEngineHandler(string msgForCaller); 325 326 //2:定义每个委托类型的成员变量 327 private CarEngineHandler listOfHandlers; 328 329 //3:向调用者添加注册函数,自定义的注册方法封装的私有委托成员变量 330 public void RegisterWithCarEngine(CarEngineHandler methodToCall) 331 { 332 listOfHandlers = methodToCall; 333 334 #region 多路广播 335 //委托支持多路广播,即一个委托对象可以维护一个可调用方法的列表而不只是单独一个方法,给一个委托对象添加多个方法时,不用直接分配(赋值操作符 = ),重载+=操作符即可 336 //listOfHandlers += methodToCall; (底层是使用的Delegate.Combine()方法) 337 //注:既然+=操作符 是添加方法 那么-=操作符则是从委托调用列表中移除成员 (底层是使用的Delegate.Remove()方法) 338 #endregion 339 } 340 341 /* 342 注:将委托定义在使用它的类型作用域里是很普遍的 343 * 严格来说,可以将委托成员变量定义为公共的,这样就不需要创建额外的注册方法,然而,如果将委托成员定义为私有的。我们就强制了封装服务并提供了更加类型安全的解决方案。 344 * 比如:C# event关键字,声明为事件 345 */ 346 347 348 349 //内部状态数据 350 public int CurrentSpeed { get; set; } //当前速度 351 public int MaxSpeed { get; set; } //最大速度 352 public string PetName { get; set; } //小汽车名字 353 354 //汽车能用还是不能用 355 private bool carIsDead; 356 357 //类的构造函数 358 public Car() { MaxSpeed = 100; } 359 public Car(string name, int maxSp, int currSp) 360 { 361 CurrentSpeed = currSp; 362 MaxSpeed = maxSp; 363 PetName = name; 364 } 365 366 //此方法:使Car对象向订阅者发送引擎相关的信息。 367 public void Accelerate(int delta) 368 { 369 //当前汽车是否不能用 370 if (carIsDead) 371 { 372 //判断当前是否有订阅者 373 if (listOfHandlers != null) 374 { 375 listOfHandlers("您的汽车已经报废"); 376 } 377 } 378 else 379 { 380 CurrentSpeed += delta; 381 382 //假设。距离最大速度剩下10 则发送警报信息 383 if (10 == (MaxSpeed - CurrentSpeed) && listOfHandlers != null) 384 { 385 listOfHandlers("不能在加速了,滴滴哒哒"); 386 } 387 if (CurrentSpeed >= MaxSpeed) carIsDead = true; 388 else Console.WriteLine("当前速度 = {0}", CurrentSpeed); 389 } 390 } 391 } 392 393 class China : Person 394 { 395 public void show() 396 { 397 if (tag == null) Console.WriteLine("派生类没有注册"); 398 else Console.WriteLine("派生类已经注册"); 399 } 400 } 401 402 class Person 403 { 404 public delegate Person DelegateHander(); 405 protected DelegateHander tag; 406 407 public void Register(DelegateHander meth) 408 { 409 tag = meth; 410 } 411 public void show() 412 { 413 if (tag == null) Console.WriteLine("父类没有注册"); 414 else Console.WriteLine("父类已经注册"); 415 } 416 } 417 418 //派生自EventArgs的类,它保存一个字符串,表示要发送给接收者的信息 419 class AnimalEventArgs : EventArgs 420 { 421 public readonly string msg; 422 public AnimalEventArgs(string message) 423 { 424 msg = message; 425 } 426 } 427 428 class Animal 429 { 430 /* 431 定义一个事件分为两个步骤: 432 * 1:需要定义一个委托类型,它包含在事件触发时将要调用的方法 433 * 2:通过Event关键字用相关委托声明这个事件 434 */ 435 436 //这个委托用来与Animal的事件协作 437 public delegate void AnimalEngineHandler(string name); 438 439 //修改委托:以符合微软推荐的事件模式 440 public delegate void AnimalEngineHandler1(object sender, AnimalEventArgs e); 441 442 //使用泛型EventHandler<T>委托,就不在需要定义一个自定义委托类型 443 public event EventHandler<AnimalEventArgs> eventHandlerDemo; 444 445 446 //声明事件 447 public event AnimalEngineHandler Call; 448 449 //修改 450 public event AnimalEngineHandler1 Call1; 451 452 //提示方法 453 public void showMsg(string name) 454 { 455 if (Call != null) 456 { 457 if (name == "熊猫") Call("杀了它你就是国宝!"); 458 else Call("^-^"); 459 } 460 } 461 //提示方法 462 public void showMsg1(string name) 463 { 464 if (Call1 != null) 465 { 466 if (name == "大熊猫") Call1(this, new AnimalEventArgs("杀了它你就是国宝")); 467 else Call1(this, new AnimalEventArgs("^-^")); 468 } 469 } 470 //提示方法 471 public void showMsg3(string name) 472 { 473 if (eventHandlerDemo != null) 474 { 475 if (name == "大熊猫") eventHandlerDemo(this, new AnimalEventArgs("杀了它你就是国宝")); 476 else eventHandlerDemo(this, new AnimalEventArgs("^-^")); 477 } 478 } 479 public string n = "测试"; 480 } 481 }