本笔记摘抄自:https://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html,记录一下学习过程以备后续查用。html
1、委托类型的来由安全
在使用C语言的年代,整个项目中都充满着针指的身影,那时候流行使用函数指针来建立回调函数,使用回调能够把函数回调给程序中的另外一个函数。但函数指针多线程
只是简单地把地址指向另外一个函数,并不能传递其余额外信息。异步
在.NET中,大部分时间里都没有指针的身影,由于指针被封闭在内部函数当中。但是回调函数却依然存在,它是以委托的方式来完成的。委托能够被视为一个更ide
高级的指针,它不只仅能把地址指向另外一个函数,并且还能传递参数、返回值等多个信息。系统还为委托对象自动生成了同步、异步的调用方式,开发人员使用函数
BeginInvoke、EndInvoke方法就能够抛开Thread而直接使用多线程调用 。工具
2、创建委托类学习
使用delegate能够直接建立委托类型,当进行系统编译时,系统就会自动生成此类型,可使用delegate void MyDelegate()方式建立一个委托类。测试
class Program { delegate void MyDelegate(); static void Main(string[] args) { Console.WriteLine("Hello World."); Console.Read(); } }
使用ILDASM.exe观察委托成员,能够看到它继承了System.MulticastDelegate类,并自动生成BeginInvoke、EndInvoke、Invoke 等三个经常使用方法。ui
Invoke 方法是用于同步调用委托对象的对应方法,而BeginInvoke、EndInvoke是用于以异步方式调用对应方法。
public class MyDelegate:MulticastDelegate { //同步调用委托方法 public virtual void Invoke(); //异步调用委托方法 public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state); public virtual void EndInvoke(IAsyncResult result); }
MulticastDelegate是System.Delegate的子类,它是一个特殊类,编译器和其余工具能够今后类派生,可是自定义类不能显式地今后类进行派生。它支持多路
广播委托,并拥有一个带有连接的委托列表。在调用多路广播委托时,系统将按照调用列表中的委托出现顺序来同步调用这些委托。
MulticastDelegate具备两个经常使用属性:Method、Target。其中Method用于获取委托所表示的方法,Target用于获取当前调用的类实例。
MulticastDelegate有如下几个经常使用方法:
方法名称 | 说明 |
---|---|
Clone | 建立委托的浅表副本。 |
GetInvocationList | 按照调用顺序返回此多路广播委托的调用列表。 |
GetMethodImpl | 返回由当前的 MulticastDelegate 表示的静态方法。 |
GetObjectData | 用序列化该实例所需的全部数据填充 SerializationInfo 对象。 |
MemberwiseClone | 建立当前 Object 的浅表副本。 |
RemoveImpl | 调用列表中移除与指定委托相等的元素 |
MulticastDelegate与Delegate给委托对象创建了强大的支持。
3、委托使用方式
3.1 简单的委托
当创建委托对象时,委托的参数类型必须与委托方法相对应。只要向创建委托对象的构造函数中输入方法名称example.Method,委托就会直接绑定此方法。
使用myDelegate.Invoke(string message),就能显式调用委托方法。
但在实际的操做中,咱们无须用到Invoke方法,而只要直接使用myDelegate(string message),就能调用委托方法。
下面代码演示简单的委托:
class Program { delegate void MyDelegateVoid(string message); public class Example { public void ShowMessage(string message) { Console.WriteLine(message); } } static void Main(string[] args) { #region 简单的委托 Example example = new Example(); MyDelegateVoid myDelegateVoid = new MyDelegateVoid(example.ShowMessage); myDelegateVoid("Hello World"); Console.Read(); #endregion } }
运行结果以下:
3.2 带返回值的委托
当创建委托对象时,委托的返回值必须与委托方法相对应。
下面代码演示带返回值的委托:
class Program { delegate string MyDelegateString(string message); public class Example { public string SayHi(string name) { return "Hello " + name; } } static void Main(string[] args) { #region 带返回值的委托 Example example = new Example(); MyDelegateString myDelegateString = new MyDelegateString(example.SayHi); string message = myDelegateString("Atomy"); Console.WriteLine(message); Console.Read(); #endregion } }
运行结果以下:
3.3 多路广播委托
在第二节前曾经提过,委托类继承于MulticastDelegate,这使委托对象支持多路广播,即委托对象能够绑定多个方法。
下面代码演示多路广播委托:
class Program { delegate double MyDelegateDouble(double message); public class Example { public double Ordinary(double price) { double price1 = 0.95 * price; Console.WriteLine($"Ordinary price={price1}"); return price1; } public double Favourable(double price) { double price1 = 0.85 * price; Console.WriteLine($"Favourable price={price1}"); return price1; } } static void Main(string[] args) { #region 多路广播委托 Example example = new Example(); MyDelegateDouble myDelegateDouble = new MyDelegateDouble(example.Ordinary); myDelegateDouble += new MyDelegateDouble(example.Favourable); Console.WriteLine($"Current Price={myDelegateDouble(100)}"); Console.Read(); #endregion } }
运行结果以下:
3.4 浅谈Observer模式(观察者模式)
简单回顾一下Observer模式,它使用一对多的方式,可让多个观察者同时关注同一个事物,并做出不一样的响应。
以下例,Manager的底薪为基本工资的1.5倍,Assistant的底薪为基本工资的1.2倍。WageManager类的RegisterWorker方法与RemoveWorker方法能够
用于注册和注销观察者,最后执行Execute方法能够对多个已注册的观察者同时输入参数。
下面代码演示使用非委托方式实现观察者模式:
class Program { #region 非委托观察者模式 /// <summary> /// 工做者类 /// </summary> public abstract class Worker { public abstract double GetWages(double basicWages); } /// <summary> /// 管理级类 /// </summary> public class Manager : Worker { public override double GetWages(double basicWages) { double totalWages = 1.5 * basicWages; Console.WriteLine($"Manager's wages is:{totalWages}"); return totalWages; } } /// <summary> /// 助理级类 /// </summary> public class Assistant : Worker { public override double GetWages(double basicWages) { double totalWages = 1.2 * basicWages; Console.WriteLine($"Assistant's wages is:{totalWages}"); return totalWages; } } /// <summary> /// 工资管理类 /// </summary> public class WageManager { IList<Worker> workerList = new List<Worker>(); public void RegisterWorker(Worker worker) { workerList.Add(worker); } public void RemoveWorker(Worker worker) { workerList.Remove(worker); } public void Excute(double basicWages) { if (workerList.Count != 0) { foreach (var worker in workerList) { worker.GetWages(basicWages); } } } } #endregion static void Main(string[] args) { #region 非委托观察者模式 WageManager wageManager = new WageManager(); //注册观察者 wageManager.RegisterWorker(new Manager()); wageManager.RegisterWorker(new Assistant()); //同时输入底薪3000元,分别进行计算。 wageManager.Excute(3000); Console.Read(); #endregion } }
运行结果以下:
开发Observer模式时若借助委托,能够进一步简化开发过程。因为委托对象支持多路广播,因此能够把Worker类省略。在WageManager类中创建了一个
委托对象wageHandler,经过Attach与Detach方法能够分别加入及取消委托。若是观察者想对事物进行监测,只须要加入一个委托对象便可。在第二节提过,
委托的GetInvodationList方法能获取多路广播委托列表,在Execute方法中,就是经过多路广播委托列表去判断所绑定的委托数量是否为0。
下面代码演示使用委托方式实现观察者模式:
class Program { #region 委托观察者模式 public delegate double Handler(double basicWages); public class Manager { public double GetWages(double basicWages) { double totalWages = 1.5 * basicWages; Console.WriteLine($"Manager's wages is:{totalWages}"); return totalWages; } } public class Assistant { public double GetWages(double basicWages) { double totalWages = 1.2 * basicWages; Console.WriteLine($"Assistant's wages is:{totalWages}"); return totalWages; } } public class WageManager { private Handler wageHandler; //加入观察者 public void Attach(Handler wageHandler1) { wageHandler += wageHandler1; } //删除观察者 public void Detach(Handler wageHandler1) { wageHandler -= wageHandler1; } //经过GetInvodationList方法获取多路广播委托列表,若是观察者数量大于0即执行方法。 public void Execute(double basicWages) { if (wageHandler != null) { if (wageHandler.GetInvocationList().Count() != 0) { wageHandler(basicWages); } } } } #endregion static void Main(string[] args) { #region 委托观察者模式 WageManager wageManager = new WageManager(); //加入Manager观察者 Manager manager = new Manager(); Handler managerHandler = new Handler(manager.GetWages); wageManager.Attach(managerHandler); //加入Assistant观察者 Assistant assistant = new Assistant(); Handler assistantHandler = new Handler(assistant.GetWages); wageManager.Attach(assistantHandler); //同时加入底薪3000元,分别进行计算 wageManager.Execute(3000); Console.ReadKey(); #endregion } }
运行结果以下:
3.5 委托的协变与逆变
在Framework 2.0出现以前,委托协变这个概念尚未出现。此时由于委托是安全类型,它们不遵照继承的基础规则。即会这下面的状况:Manager虽然
是Worker的子类,但GetWorkerHander委托不能直接绑定GetManager方法,由于在委托当中它们的返回值Manager与Worker被视为彻底无关的两个类型。
自Framework 2.0面世之后,委托协变的概念就应运而生,此时委托能够按照传统的继承规则进行转换。即GetWorkerHandler委托能够直接绑定
GetManager方法。
下面代码演示委托的协变:
class Program { #region 委托的协变 /// <summary> /// 在Framework 2.0以上可绑定GetWorker与GetManager两个方法 /// </summary> /// <param name="id"></param> /// <returns></returns> public delegate Worker GetWorkerHandler(int id); public class Worker { public Worker() { } public Worker(int id) { Id = id; } public int Id { get; set; } public void ShowId() { Console.WriteLine($"Id={Id}"); } } public class Manager : Worker { public Manager() { } public Manager(int id) { Id = id; } } public static Worker GetWorker(int id) { Worker worker = new Worker(id); return worker; } public static Manager GetManager(int id) { Manager manager = new Manager(id); return manager; } #endregion static void Main(string[] args) { #region 委托的协变 GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker); Worker worker = workerHandler(1); worker.ShowId(); GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager); Manager manager = managerHandler(2) as Manager; manager.ShowId(); Console.Read(); #endregion } }
运行结果以下:
委托逆变,是指委托方法的参数一样能够接收 “继承” 这个传统规则。像下面的例子,以object为参数的委托,能够接受任何object子类的对象做为参数。
最后能够在处理方法中使用is对输入数据的类型进行判断,分别处理对不一样的类型的对象。
下面代码演示委托的逆变:
class Program { #region 委托的逆变 public delegate void Handler(object obj); public static void GetMessage(object message) { if (message is string) Console.WriteLine("His name is:" + message.ToString()); if (message is int) Console.WriteLine("His age is:" + message.ToString()); } #endregion static void Main(string[] args) { #region 委托的逆变 Handler handler = new Handler(GetMessage); handler(29); Console.Read(); #endregion } }
运行结果以下:
注:委托与其绑定方法的参数必须一至,即当 Handler 所输入的参数为 A 类型,其绑定方法 GetMessage 的参数也必须为 A 类或者 A 的父类 。相反,
当绑定方法的参数为 A 的子类,系统也没法辨认。
3.6 泛型委托
委托逆变虽然实用,但若是都以object做为参数,则须要每次都对参数进行类型的判断,这不由使人感到厌烦。
为此,泛型委托应运而生,泛型委托有着委托逆变的优势,同时利用泛型的特性,可使一个委托绑定多个不一样类型参数的方法,并且在方法中不须要
使用is进行类型判断,从而简化了代码。
下面代码演示泛型委托:
class Program { #region 泛型委托 public delegate void Handler<T>(T obj); /// <summary> /// 工做者类 /// </summary> public class Worker { public double Wages { get; set; } } /// <summary> /// 管理级类 /// </summary> public class Manager : Worker { } public static void GetWorkerWages(Worker worker) { Console.WriteLine("Worker's total wages is:" + worker.Wages); } public static void GetManagerWages(Manager manager) { Console.WriteLine("Manager's total wages is:" + manager.Wages); } #endregion static void Main(string[] args) { #region 泛型委托 Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages); Worker worker = new Worker { Wages = 3000 }; workerHander(worker); Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages); Manager manager = new Manager { Wages = 4500 }; managerHandler(manager); Console.ReadKey(); #endregion } }
运行结果以下:
4、深刻解析事件
4.1 事件的由来
在介绍事件以前你们能够先看看下面的例子,PriceManager负责对商品价格进行处理,当委托对象GetPriceHandler的返回值大于100元,按8.8折计算,
低于100元按原价计算。
class Program { #region 事件的由来 public delegate double PriceHandler(); public class PriceManager { public PriceHandler GetPriceHandler; //委托处理,当价格高于100元按8.8折计算,其余按原价计算。 public double GetPrice() { if (GetPriceHandler.GetInvocationList().Count() > 0) { if (GetPriceHandler() > 100) return GetPriceHandler() * 0.88; else return GetPriceHandler(); } return -1; } } //书本价格为98元 public static double BookPrice() { return 98.0; } //计算机价格为8800元 public static double ComputerPrice() { return 8800.0; } #endregion static void Main(string[] args) { #region 事件的由来 PriceManager priceManager = new PriceManager { //调用priceManager的GetPrice方法获取价格 //直接调用委托的Invoke获取价格,二者进行比较。 GetPriceHandler = new PriceHandler(ComputerPrice) }; Console.WriteLine(string.Format("GetPrice\n Computer's price is {0}",priceManager.GetPrice())); Console.WriteLine(string.Format("Invoke\n Computer's price is {0}",priceManager.GetPriceHandler.Invoke())); Console.WriteLine(); priceManager.GetPriceHandler = new PriceHandler(BookPrice); Console.WriteLine(string.Format("GetPrice\n Book's price is {0}",priceManager.GetPrice())); Console.WriteLine(string.Format("Invoke\n Book's price is {0}",priceManager.GetPriceHandler.Invoke())); Console.Read(); #endregion } }
运行结果以下:
观察运行的结果,若是把委托对象GetPriceHandler设置为public,外界能够直接调用GetPriceHandler.Invoke获取运行结果而移除了GetPrice方法的处理,
这正是开发人员最不想看到的。
为了保证系统的封装性,开发每每须要把委托对象GetPriceHandler设置为private,再分别加入AddHandler、RemoveHandler方法对GetPriceHandler委托
对象进行封装。为了保存封装性,不少操做都须要加入AddHandler、RemoveHandler这些类似的方法代码,这未免使人感到厌烦。
为了进一步简化操做,事件这个概念应运而生。
4.2 事件的定义
事件(event)可被视做为一种特别的委托,它为委托对象隐式地创建起add_XXX、remove_XXX两个方法,用做注册与注销事件的处理方法,并且事件对
应的变量成员将会被视为private变量,外界没法超越事件所在对象直接访问它们,这使事件具有良好的封装性,并且免除了add_XXX、remove_XXX等繁琐
的代码。
#region 事件的定义 public class EventTest { public delegate void MyDelegate(); public event MyDelegate MyEvent; } #endregion
使用ILDASM.exe观察事件成员,系统为MyEvent事件自动创建add_MyEvent、remove_MyEvent 方法。
4.3 事件的使用方式
事件能经过+=和-=两个方式注册及注销对其处理的方法,使用+=与-=操做符的时候,系统会自动调用对应的add_XXX、remove_XXX进行处理。
值得留意,在PersonManager类的Execute方法中,若是MyEvent绑定的处理方法不为空,便可使用MyEvent(string)引起事件。但若是在外界的Main方法中
直接使用personManager.MyEvent(string)来引起事件,系统将引起错误报告。这正是由于事件具有了良好的封装性,使外界不能超越事件所在的对象访问其变
量成员。
注:在事件所处的对象以外,事件只能出如今+=、-=的左方。
下面代码演示事件的使用:
class Program { #region 事件的使用 public delegate void MyDelegate(string name); public class PersonManager { public event MyDelegate MyEvent; //执行事件 public void Execute(string name) { if (MyEvent != null) { MyEvent(name); } } } public static void GetName(string name) { Console.WriteLine("My name is " + name); } #endregion static void Main(string[] args) { #region 事件的使用 PersonManager personManager = new PersonManager(); //绑定事件处理方法 personManager.MyEvent += new MyDelegate(GetName); personManager.Execute("Atomy"); Console.Read(); #endregion } }
运行结果以下:
4.4 事件处理方法的绑定
在绑定事件处理方法的时候,事件出如今+=、-= 操做符的左边,对应的委托对象出如今+=、-= 操做符的右边。对应以上例子,事件提供了更简单的绑定方式,
只须要在+=、-= 操做符的右方写上方法名称,系统就能自动辩认。
下面代码演示事件处理方法的绑定:
class Program { #region 事件的使用及方法绑定 public delegate void MyDelegate(string name); public class PersonManager { public event MyDelegate MyEvent; //执行事件 public void Execute(string name) { if (MyEvent != null) { MyEvent(name); } } } public static void GetName(string name) { Console.WriteLine("My name is " + name); } #endregion static void Main(string[] args) { #region 事件的使用及方法绑定 PersonManager personManager = new PersonManager(); //绑定事件处理方法方式一 personManager.MyEvent += new MyDelegate(GetName); //绑定事件处理方法方式二 personManager.MyEvent += GetName; personManager.Execute("Atomy"); Console.Read(); #endregion } }
运行结果以下:
若是以为编写GetName方法过于麻烦,还可使用匿名方法绑定事件的处理。
下面代码演示事件处理方法的匿名方法绑定:
class Program { #region 事件的使用 public delegate void MyDelegate(string name); public class PersonManager { public event MyDelegate MyEvent; //执行事件 public void Execute(string name) { if (MyEvent != null) { MyEvent(name); } } } public static void GetName(string name) { Console.WriteLine("My name is " + name); } #endregion static void Main(string[] args) { #region 事件的使用及方法绑定 PersonManager personManager = new PersonManager(); //绑定事件处理方法方式一 personManager.MyEvent += new MyDelegate(GetName); //绑定事件处理方法方式二 personManager.MyEvent += GetName; //绑定事件处理方法方式三(匿名方法) personManager.MyEvent += delegate (string name) { Console.WriteLine("My name is " + name); }; personManager.Execute("Atomy"); Console.Read(); #endregion } }
运行结果以下:
4.5 C#控件中的事件
在C#控件中存在不少的事件,好比Click、TextChanged、SelectIndexChanged等等,不少都是经过EventHandler委托绑定事件的处理方式,EventHandler可说是C#控件中最多见的委托 。
public delegate void EventHandler (Object sender, EventArgs e)
EventHandler委托并没有返回值,sender表明引起事件的控件对象,e表明由该事件生成的数据 。
下面代码演示C#控件中的事件绑定:
public partial class EventTest : Form { public EventTest() { InitializeComponent(); } private void EventTest_Load(object sender, EventArgs e) { btnEvent.Click += new EventHandler(btnEvent_onclick); } public void btnEvent_onclick(object sender,EventArgs e) { Button button = (Button)sender; MessageBox.Show(button.Text); } }
运行结果以下:
EventHandler只是EventHandler<TEventArgs>泛型委托的一个简单例子。事实上,你们能够利用 EventHandler<TEventArgs> 构造出所须要的委托。
public delegate void EventHandler<TEventArgs> (Object sender, TEventArgs e)
在EventHandler<TEventArgs>中,sender表明事件源,e表明派生自EventArgs类的事件参数。开发人员能够创建派生自EventArgs的类,从中加入须要使用到的事件参数,而后创建
EventHandler<TEventArgs>委托。
下面的例子中,先创建一个派生自EventArgs的类MyEventArgs做为事件参数,而后在EventManager中创建事件myEvent , 经过Execute方法能够激发事件。最后在测试中绑定myEvent
的处理方法ShowMessage,在ShowMessage显示myEventArgs的事件参数Message。
class Program { #region EventArgs派生 public class MyEventArgs : EventArgs { private string args; public MyEventArgs(string message) { args = message; } public string Message { get { return args; } set { args = value; } } } public class EventManager { public event EventHandler<MyEventArgs> myEvent; public void Execute(string message) { myEvent?.Invoke(this, new MyEventArgs(message)); } } public static void ShowMessage(object obj, MyEventArgs e) { Console.WriteLine(e.Message); } #endregion static void Main(string[] args) { #region EventArgs派生 EventManager eventManager = new EventManager(); eventManager.myEvent += new EventHandler<MyEventArgs>(ShowMessage); eventManager.Execute("How are you?"); Console.Read(); #endregion } }
运行结果以下:
4.6 为用户控件创建事件
开发过程当中,每每会出现不少相似的控件与代码,开发人员能够经过用户控件来避免重复的代码。但每每同一个用户控件,在不一样的页面中须要有不一样的响应。此时为用户控件创建事件,
即可轻松地解决此问题。