以下内容只是个人理解,仅供参考。
什么是委托?
先看最简单的委托例子:
namespace DelegateTest { public delegate void MessageDelegate(string name); class Program { private static void SaySomething(string name) { Console.WriteLine("You said " + name); } static void Main(string[] args) { MessageDelegate msgDelegate = new MessageDelegate(SaySomething); msgDelegate("Hello"); Console.ReadKey(); } } }
输出结果为:You said Hello
从代码中我们可以看到,SaySomething是一个方法,delegate是一个类。
通俗的来说,委托是方法的容器,就像数组string[]是string变量的容器。
如返回值void类型和接收string类型参数的委托对象只能绑定同样类型的方法。
MessageDelegate msgDelegate =
new MessageDelegate(SaySomething);
这句代码是初始化委托对象,我们把SaySomething这个方法封装进去msgDelegate中。
它的构造方法必须要有1个方法作为参数才能初始化。
初始化一个委托对象也可以直接赋值一个方法初始化,如:
MessageDelegate msgDelegate=SaySomething;
这个时候SaySomething方法已经装入了委托对象msgDelegate内,所以们可以通过调用委托对象来调用已装入的方法。
我们要使用的时候就把委托对象msgDelegate当做方法一样调用。
过程总结:
1.定义委托对象
public delegate void MessageDelegate(string name);
2.定义方法
private static void SaySomething(string name)
{
Console.WriteLine("You said " + name);
}
3.新建一个委托对象然后初始化
MessageDelegate msgDelegate =
new MessageDelegate(SaySomething);
或者
MessageDelegate msgDelegate=SaySomething;
4.调用委托对象
msgDelegate(“Hello”);
以上是不带返回值的委托,如果要带返回值的委托可以自己制定:
public delegate int AddNumber(int a,int b); static void Main(string[] args) { AddNumber an= new AddNumber(MyFunc); Int n=an(1,2); Console.WriteLine(n); Console.ReadKey(); } private static int AddFunc(int a,int b) { return a+b; }
多路广播委托:
如果说委托对象是一个容器,那一个委托对象可以绑定多个方法。
很简单,我们使用+=来绑定更多的对象。
反之,我们可以用-=来取消绑定对象。
namespace DelegateTest { class Program { public delegate void MessageDelegate(string name); private static void SaySomething(string name) { Console.WriteLine("You said " + name); } private static void SayAnything(string str) { Console.WriteLine(str); } static void Main(string[] args) { MessageDelegate msgDelegate = new MessageDelegate(SaySomething); msgDelegate += SayAnything; msgDelegate("Meh"); Console.ReadKey(); } } }
输出结果为:
You said Meh
Meh
注意:委托对象的列表为空(null)的时候不能调用,但可以绑定或取消绑定对象。
既然委托对象是一个类,那么我们也可以把这个委托对象作为一个方法的参数来传递。
namespace DelegateTest { class Program { public delegate void MessageDelegate(string name); private static void SaySomething(string name) { Console.WriteLine("You said " + name); } private static void SayAnything(MessageDelegate msgDelegate) { if(msgDelegate!=null) msgDelegate("Hello"); } static void Main(string[] args) { MessageDelegate msgDelegate = new MessageDelegate(SaySomething); SayAnything(msgDelegate); SayAnything(SaySomething); msgDelegate.Invoke("xxx"); msgDelegate("xxx"); Console.ReadKey(); } } }
输出结果为:
You said Hello
You said Hello
xxx
xxx
我们用了4种不同的方法依次调用,第一个是传入一个委托对象,第二个是传入方法,第三个是直接调用委托,第四个和第三个是等价的。
由此可看出我们可以把方法作为参数传入一个委托对象类型来调用,也可以把一个装有方法的委托类型作为参数传递后调用。
总结:
委托对象是一个类,可以传递与委托对象返回值和形参相同的方法,然后去调用它。
委托可以绑定多个方法然后依次调用。
委托是函数指针,主要功能是用来实现类之间的交流和回调,就像你委托朋友帮你做一件事,你朋友会告诉你进度。
为什么要用委托?什么时候用委托?
看了上面的例子,大部分同学应该对委托大概有个理解了,可能有同学会问为什么不直接调用SaySomething而要通过委托对象间接调用,用了委托到底有什么好处,什么时候该用委托,其实从字面上委托的意思是两方交流的中介,比如中国和俄罗斯交流需要委托翻译来实现,接下来我们再看几个例子。
一、当你不确定使用具体方法的时候
比如你是一所幼儿园老师,你想奖励小朋友食物,有以下几种奖励:
private static void GiveLolipop() { //给了棒棒糖 Console.WriteLine("给了棒棒糖"); } private static void GiveCake() { //给了蛋糕 Console.WriteLine("给了蛋糕"); } private static void GiveSugar() { //给了糖果 Console.WriteLine("给了糖果"); } private static void GiveBiscuit() { //给了饼干 Console.WriteLine("给了饼干"); }
如果我们想给每个小孩定制一套奖励,可以定义多个方法实现
private static void GiveChildren1() { GiveSugar(); GiveBiscuit(); GiveLolipop(); } private static void GiveChildren2() { GiveSugar(); GiveCake(); GiveBiscuit(); } private static void GiveChildren3() { GiveLolipop(); GiveSugar(); GiveCake(); }
和使用enum及switch语句
public enum Children{ Jojo,Meme,Kiki } private static void GiveChildren(Children children) { switch(children) { case Children.Jojo: GiveChildren1(); break; case Children.Meme: GiveChildren2(); break; case Children.Kiki: GiveChildren3(); break; default: GiveSugar(); break; } }
虽然这样做可以解决需求,但是扩展性非常差,我们想增加新的一套奖励就得增加新的方法和修改枚举类型和switch语句。
如果我们使用委托,则可以解决这个问题:
因为可以动态决定使用哪个方法,所以这里我们不需要枚举。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DelegateTest { class Program { public delegate void RewardDelegate(); static void Main(string[] args) { RewardDelegate rewardAll,reward1, reward2, reward3; rewardAll = GiveSugar; rewardAll += GiveBiscuit; rewardAll += GiveCake; rewardAll += GiveLolipop; reward1 = rewardAll - GiveCake; reward2 = rewardAll - GiveLolipop; reward3 = rewardAll - GiveBiscuit; GiveChildren("Jojo", reward2); Console.ReadKey(); } private static void GiveLolipop() { //给了棒棒糖 Console.WriteLine("给了棒棒糖"); } private static void GiveCake() { //给了蛋糕 Console.WriteLine("给了蛋糕"); } private static void GiveSugar() { //给了糖果 Console.WriteLine("给了糖果"); } private static void GiveBiscuit() { //给了饼干 Console.WriteLine("给了饼干"); } private static void GiveChildren(string name,RewardDelegate rD) { if (rD != null) { Console.WriteLine("给" + name + "的奖励有:"); rD.Invoke(); } } } }
运行结果如图:
这个例子可能举得不太好,但我们可以动态地决定使用哪个方法,代码也简洁了很多,让程序的扩展性更灵活。
所以委托可以让我们避免大量使用条件语句(if,switch),同时也可以避免代码的重复性。
如果还有同学不明白的话,我再举一个简单的例子。
假设你需要装修房子,你需要给装修工人一个装修方案,那么代码可以这么写:
public delegate void DecorateDelegate(); static void Main(string[] args) { DecorateHouse(ClassicStyle); Console.ReadKey(); } private void ClassicStyle() { Console.WriteLine("你选择了经典装修方案"); } private void DecorateHouse(DecorateDelegate dd) { if (dd != null) dd.Invoke(); }
当你需要更改装修方案时你只需要更改DecorateHouse里的参数就可以了,操作起来简单很多。
二、当你需要实现两个类之间的沟通时
接下来我们再看最后一个例子:
namespace DelegateTest2 { class Program { static void Main(string[] args) { Myclass myClass = new Myclass(); myClass.Working(); } } class Myclass { public void Working() { for(int i=0;i<10000;i++) { //处理事件 } } } }
在这样一个例子中,如果Program类想在循环运行的时候想要实时获得Myclass中Working方法里的信息,我们可以使用委托来实现。
class Program { static void Main(string[] args) { Myclass myClass = new Myclass(); myClass.Working(CallBack); } private static void CallBack(int i) { Console.WriteLine(i); } } class Myclass { public delegate void CallBack(int i); public void Working(CallBack callBack) { for(int i=0;i<10000;i++) { //处理事件 callBack(i); } } }
我们把Program类中的CallBack方法传入了Working方法中,由此得到回调。
简单来说就是就是MyClass使用了Program中的CallBack方法来输出信息。
别问我为什么不直接public static 然后直接调用,我们考虑的是对象的封装性。
程序运行结果如图:
总结:除了将方法作为参数传递外,委托的主要功能是实现两方之间的通讯,然后实现回调。
对于委托的深入理解
==================================================================
1.委托的协变与逆变
在Framework 2.0前的版本,还没有委托协变的概念
举个例子:
public class Human
{.......}
public class Man :
Human
{.......}
public delegate Human HumanHandler(int id);
public delegate Man ManHandler(int id);
在这里HumanHandler是不能绑定返回值为Man的方法,因为它们被视为两个不同的类型,虽然Man继承至Human。
而在Framework 2.0后的版本,有了委托协变的概念
我们可以直接绑定返回值为Man的方法:
HumanHandler hh=new HumanHandler(ManHandler);
Man man=hh as Man;
而委托逆变跟委托协变一样,唯一不同的是它以object作为参数的委托,然后再用is判断object的类型。
class Program { 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()); } static void Main(string[] args) { Handler handler = new Handler(GetMessage); handler(29); Console.ReadKey(); } }
2.泛型委托
对于以上的方法,如果都以object作为参数,每次都要进行拆箱操作是非常消耗性能的,过程也很繁琐。
因此这里引入了泛型委托的概念。
class Program { public delegate void Handler<T>(T obj); static void Main(string[] args) { Handler<int> handler1 = new Handler<int>(getSquare); Handler<string> handler2 = new Handler<string>(sayHi); handler1(2); handler2("Wix"); Console.ReadKey(); } static void getSquare(int a) { Console.WriteLine(a * a); } static void sayHi(string name) { Console.WriteLine("Hi,"+name); } }
输出结果如图:
什么时候使用泛型委托?
如果你想绑定多个不同类型参数方法的话可以使用泛型委托,而且不需要用is进行类型判断。这样我们就可以不用定义多个不同参数类型的委托了。
什么是事件?
简单的来说,事件的由来是为了保证系统的封装性。
上面的代码可以看到委托的声明都是public,这使得外界可以直接进行调用或赋值操作。
如果设置成private,我们需要添加AddHandler和RemoveHandler方法(+=和-=),就有如get与set方法,很麻烦。
所以事件这个概念由此而生。
public class EventTest { public delegate void MyDelegate(); public event MyDelegate MyEvent; }
*事件对应的变量成员将会被视为 private 变量
如果不明白,可以想象事件是委托的容器,可以保证委托的封装性。
事件能通过+=和-=两个方式注册或者注销对其处理的方法
public delegate void MyDelegate(string name); public class PersonManager { public event MyDelegate MyEvent; //执行事件 public void Execute(string name) { if (MyEvent != null) MyEvent(name); } } class Program { static void Main(string[] args) { PersonManager personManager = new PersonManager(); //绑定事件处理方法 personManager.MyEvent += new MyDelegate(GetName); personManager.Execute("Leslie"); Console.ReadKey(); } public static void GetName(string name) { Console.WriteLine("My name is " + name); } }
我们也可以直接绑定方法
personManager.MyEvent += GetName;
或者绑定匿名方法
personManager.MyEvent += delegate(string name){
Console.WriteLine("My name is "+name);
};
总结:事件就是一个特殊的委托。
什么时候用事件?
事件可以把逻辑流程拆分成几个阶段,在游戏中,如单位生产事触发某个事件,单位死亡时触发某个事件。
下面是模拟unity中的代码例子:
class UnitHandler { public delegate void UnitEventHandler(GameObject unit); public static event UnitEventHandler onUnitSpawn; public static event UnitEventHandler onUnitDestroy; public static void NewUnitCreated(GameObject unit) { if (onUnitSpawn != null) onUnitSpawn(unit); } public static void UnitDead(GameObject unit) { if(onUnitDestroy!=null) onUnitDestroy(unit); } } class UnitManager { public void OnEnabled() { UnitHandler.onUnitSpawn += this.NewUnitCreated; } public void NewUnitCreated(GameObject unit) { Console.WriteLine("unit created"); Console.ReadKey(); } public void OnDisable() { UnitHandler.onUnitSpawn -= this.NewUnitCreated; } } class Program { static void Main(string[] args) { UnitManager um = new UnitManager(); um.OnEnabled(); UnitHandler.NewUnitCreated(new GameObject()); um.OnDisable(); UnitHandler.NewUnitCreated(new GameObject()); } }
*这里的GameObject是一个空类
运行结果为:
unit created
事件可以在某件事件发生时让一个对象通知另一个对象,可以理解为监听某个事件,然后在特定条件下触发。
下面再看一个例子:
在我们创建一个事件之前,我们需要一个委托,而一般标准的委托声明如下:
public delegate void EventHandler(object sender,
System.EventArgs e);
第一个形参object sender定义了对象来源,第二个形参放的是继承自System.EventArgs的类,一般上这个类包含了事件的详细信息。
例子:
class ButtonEventArgs:EventArgs
{
public string time;
}
在这里我们不需要传递什么事件信息,所以我们用基类EventArgs就好。
public delegate void EventHandler(object sender, System.EventArgs e); class Publisher { public event EventHandler Added; //定义发生事件 protected virtual void OnAdded(System.EventArgs e) //当事件发生中触发方法 { if(Added!=null) { Added(this, e); } } public void Add(object value) //触发事件的方法 { OnAdded(System.EventArgs.Empty); } } class Subscriber { void AddedEventHandler(object sender,System.EventArgs e) { System.Console.WriteLine("AddEvent occured"); } static void Main() { Subscriber s = new Subscriber(); Publisher p = new Publisher(); p.Added += s.AddedEventHandler; p.Add(10); } }
事件的使用步骤如下:
存放事件的类
1.定义事件
2.触发事件的方法(protected)
3.间接触发事件的方法(public)
触发事件的类
1.定义方法
2.注册方法
3.触发方法
再来看最后一个例子:
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) { if (myEvent != null) myEvent(this, new MyEventArgs(message)); } } class Program { static void Main(string[] args) { EventManager eventManager = new EventManager(); eventManager.myEvent += new EventHandler<MyEventArgs>(ShowMessage); eventManager.Execute("How are you!"); Console.ReadKey(); } public static void ShowMessage(object obj,MyEventArgs e) { Console.WriteLine(e.Message); } }
这里我们使用了EventHandler<TEventArgs> 构造出所需要的委托。
public delegate void EventHandler<TEventArgs> (Object sender, TEventArgs e)
MyEventArgs 负责存放事件信息。
EventManager负责事件的定义和执行。
Program 负责定义方法和事件的触发。
总结:
委托是派生自System.MultcastDelegate 的类,事件(Event)属于一种特殊的委托,它与委托类型同步使用。
本文章部分内容参考了此文章并加入了个人理解:
http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html