C# 委托 学习

为什么要使用委托?因为方法无法像参数一样传递。


“把方法当作参数在另一个方法中传递或调用”

通俗的来说,委托是方法的容器


转:https://www.cnblogs.com/ruanraun/p/6037075.html


http://blog.csdn.net/wiiix/article/details/51463977


以下内容只是个人理解,仅供参考。


什么是委托?

先看最简单的委托例子:

[csharp]  view plain  copy
  1. namespace DelegateTest  
  2. {  
  3.     public delegate void MessageDelegate(string name);  
  4.     class Program  
  5.     {  
  6.         private static void SaySomething(string name)  
  7.         {  
  8.             Console.WriteLine("You said " + name);  
  9.         }  
  10.         static void Main(string[] args)  
  11.         {  
  12.             MessageDelegate msgDelegate = new MessageDelegate(SaySomething);  
  13.             msgDelegate("Hello");  
  14.             Console.ReadKey();  
  15.         }  
  16.     }  
  17. }  

输出结果为: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”);

或者msgDelegate.Invoke(“Hello”);


BeginInvoke和EndInvoke属于异步调用使用。

以上是不带返回值的委托,如果要带返回值的委托可以自己制定:

[csharp]  view plain  copy
  1. public delegate int AddNumber(int a,int b);  
  2.   
  3.   static void Main(string[] args)  
  4.         {  
  5.             AddNumber an= new AddNumber(MyFunc);  
  6.            Int n=an(1,2);  
  7. Console.WriteLine(n);  
  8.            Console.ReadKey();  
  9.         }  
  10.   private static int AddFunc(int a,int b)  
  11.         {  
  12.             return a+b;          
  13. }  

多路广播委托:

如果说委托对象是一个容器,那一个委托对象可以绑定多个方法。

很简单,我们使用+=来绑定更多的对象。

反之,我们可以用-=来取消绑定对象。


[csharp]  view plain  copy
  1. namespace DelegateTest  
  2. {  
  3.   
  4.     class Program  
  5.     {  
  6.         public delegate void MessageDelegate(string name);  
  7.         private static void SaySomething(string name)  
  8.         {  
  9.             Console.WriteLine("You said " + name);  
  10.         }  
  11.         private static void SayAnything(string str)  
  12.         {  
  13.             Console.WriteLine(str);  
  14.         }  
  15.         static void Main(string[] args)  
  16.         {  
  17.             MessageDelegate msgDelegate = new MessageDelegate(SaySomething);  
  18.             msgDelegate += SayAnything;  
  19.             msgDelegate("Meh");  
  20.             Console.ReadKey();  
  21.         }  
  22.     }  
  23. }  

输出结果为:

You said Meh

Meh

注意:委托对象的列表为空(null)的时候不能调用,但可以绑定或取消绑定对象。

既然委托对象是一个类,那么我们也可以把这个委托对象作为一个方法的参数来传递。

[csharp]  view plain  copy
  1. namespace DelegateTest  
  2. {  
  3.   
  4.     class Program  
  5.     {  
  6.         public delegate void MessageDelegate(string name);  
  7.         private static void SaySomething(string name)  
  8.         {  
  9.             Console.WriteLine("You said " + name);  
  10.         }  
  11.            private static void SayAnything(MessageDelegate msgDelegate)  
  12.         {  
  13.             if(msgDelegate!=null)  
  14.             msgDelegate("Hello");  
  15.         }  
  16.   
  17.         static void Main(string[] args)  
  18.         {  
  19.             MessageDelegate msgDelegate = new MessageDelegate(SaySomething);  
  20.             SayAnything(msgDelegate);  
  21.             SayAnything(SaySomething);  
  22.             msgDelegate.Invoke("xxx");  
  23.             msgDelegate("xxx");  
  24.             Console.ReadKey();  
  25.         }  
  26.     }  
  27. }  

输出结果为:

You said Hello

You said Hello

xxx

xxx

我们用了4种不同的方法依次调用,第一个是传入一个委托对象,第二个是传入方法,第三个是直接调用委托,第四个和第三个是等价的。

由此可看出我们可以把方法作为参数传入一个委托对象类型来调用,也可以把一个装有方法的委托类型作为参数传递后调用。

总结:

委托对象是一个类,可以传递与委托对象返回值和形参相同的方法,然后去调用它。

委托可以绑定多个方法然后依次调用。

委托是函数指针,主要功能是用来实现类之间的交流和回调,就像你委托朋友帮你做一件事,你朋友会告诉你进度。

为什么要用委托?什么时候用委托?

看了上面的例子,大部分同学应该对委托大概有个理解了,可能有同学会问为什么不直接调用SaySomething而要通过委托对象间接调用,用了委托到底有什么好处,什么时候该用委托,其实从字面上委托的意思是两方交流的中介,比如中国和俄罗斯交流需要委托翻译来实现,接下来我们再看几个例子。

一、当你不确定使用具体方法的时候

比如你是一所幼儿园老师,你想奖励小朋友食物,有以下几种奖励:

[csharp]  view plain  copy
  1. private static void GiveLolipop()  
  2.         {  
  3.             //给了棒棒糖  
  4.             Console.WriteLine("给了棒棒糖");  
  5.         }  
  6.         private static void GiveCake()  
  7.         {  
  8.             //给了蛋糕  
  9.             Console.WriteLine("给了蛋糕");  
  10.         }  
  11.         private static void GiveSugar()  
  12.         {  
  13.             //给了糖果  
  14.             Console.WriteLine("给了糖果");  
  15.         }  
  16.         private static void GiveBiscuit()  
  17.         {  
  18.             //给了饼干  
  19.             Console.WriteLine("给了饼干");  
  20.         }  

如果我们想给每个小孩定制一套奖励,可以定义多个方法实现

[csharp]  view plain  copy
  1. private static void GiveChildren1()  
  2.       {  
  3.           GiveSugar();  
  4.           GiveBiscuit();  
  5.           GiveLolipop();  
  6.       }  
  7.       private static void GiveChildren2()  
  8.       {  
  9.           GiveSugar();  
  10.           GiveCake();  
  11.           GiveBiscuit();  
  12.       }  
  13.       private static void GiveChildren3()  
  14.       {  
  15.           GiveLolipop();  
  16.           GiveSugar();  
  17.           GiveCake();  
  18.       }  

和使用enumswitch语句

[csharp]  view plain  copy
  1. public enum Children{  
  2. Jojo,Meme,Kiki  
  3. }  
  4.   
  5. private static void GiveChildren(Children children)  
  6. {  
  7.  switch(children)  
  8.             {  
  9.                 case Children.Jojo:  
  10.                     GiveChildren1();  
  11.                     break;  
  12.                 case Children.Meme:  
  13.                     GiveChildren2();  
  14.                     break;  
  15.                         case Children.Kiki:  
  16.                     GiveChildren3();  
  17.                     break;  
  18.                 default:  
  19.                     GiveSugar();  
  20.                     break;  
  21.             }  
  22. }  

虽然这样做可以解决需求,但是扩展性非常差,我们想增加新的一套奖励就得增加新的方法和修改枚举类型和switch语句。

如果我们使用委托,则可以解决这个问题:

因为可以动态决定使用哪个方法,所以这里我们不需要枚举。

[csharp]  view plain  copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace DelegateTest  
  8. {  
  9.   
  10.     class Program  
  11.     {  
  12.         public delegate void RewardDelegate();  
  13.   
  14.         static void Main(string[] args)  
  15.         {  
  16.             RewardDelegate rewardAll,reward1, reward2, reward3;  
  17.             rewardAll = GiveSugar;  
  18.             rewardAll += GiveBiscuit;  
  19.             rewardAll += GiveCake;  
  20.             rewardAll += GiveLolipop;  
  21.             reward1 = rewardAll - GiveCake;  
  22.             reward2 = rewardAll - GiveLolipop;  
  23.             reward3 = rewardAll - GiveBiscuit;  
  24.             GiveChildren("Jojo", reward2);  
  25.             Console.ReadKey();  
  26.         }  
  27.       
  28.         private static void GiveLolipop()  
  29.         {  
  30.             //给了棒棒糖  
  31.             Console.WriteLine("给了棒棒糖");  
  32.         }  
  33.         private static void GiveCake()  
  34.         {  
  35.             //给了蛋糕  
  36.             Console.WriteLine("给了蛋糕");  
  37.         }  
  38.         private static void GiveSugar()  
  39.         {  
  40.             //给了糖果  
  41.             Console.WriteLine("给了糖果");  
  42.         }  
  43.         private static void GiveBiscuit()  
  44.         {  
  45.             //给了饼干  
  46.             Console.WriteLine("给了饼干");  
  47.         }  
  48.         
  49.         private static void GiveChildren(string name,RewardDelegate rD)  
  50.         {  
  51.             if (rD != null)  
  52.             {  
  53.                 Console.WriteLine("给" + name + "的奖励有:");  
  54.                 rD.Invoke();  
  55.             }  
  56.         }  
  57.     }  
  58. }  

运行结果如图:


这个例子可能举得不太好,但我们可以动态地决定使用哪个方法,代码也简洁了很多,让程序的扩展性更灵活。

所以委托可以让我们避免大量使用条件语句(if,switch),同时也可以避免代码的重复性。

如果还有同学不明白的话,我再举一个简单的例子。

假设你需要装修房子,你需要给装修工人一个装修方案,那么代码可以这么写:

[csharp]  view plain  copy
  1. public delegate void DecorateDelegate();  
  2.        static void Main(string[] args)  
  3.        {  
  4.            DecorateHouse(ClassicStyle);  
  5.            Console.ReadKey();  
  6.        }  
  7.        private void ClassicStyle()  
  8.        {  
  9.            Console.WriteLine("你选择了经典装修方案");  
  10.        }  
  11.        private void DecorateHouse(DecorateDelegate dd)  
  12.        {  
  13.            if (dd != null)  
  14.                dd.Invoke();  
  15.        }  

当你需要更改装修方案时你只需要更改DecorateHouse里的参数就可以了,操作起来简单很多。

二、当你需要实现两个类之间的沟通时

接下来我们再看最后一个例子:

[csharp]  view plain  copy
  1. namespace DelegateTest2  
  2. {  
  3.     class Program  
  4.     {  
  5.         static void Main(string[] args)  
  6.         {  
  7.             Myclass myClass = new Myclass();  
  8.             myClass.Working();  
  9.         }  
  10.     }  
  11.     class Myclass  
  12.     {  
  13.         public void Working()  
  14.         {  
  15.             for(int i=0;i<10000;i++)  
  16.             {  
  17.                 //处理事件  
  18.             }  
  19.         }  
  20.     }  
  21. }  


在这样一个例子中,如果Program类想在循环运行的时候想要实时获得MyclassWorking方法里的信息,我们可以使用委托来实现。

[csharp]  view plain  copy
  1. class Program  
  2.     {  
  3.          
  4.         static void Main(string[] args)  
  5.         {  
  6.             Myclass myClass = new Myclass();  
  7.             myClass.Working(CallBack);  
  8.         }  
  9.         private static void CallBack(int i)  
  10.         {  
  11.             Console.WriteLine(i);  
  12.         }  
  13.     }  
  14.     class Myclass  
  15.     {  
  16.         public delegate void CallBack(int i);  
  17.         public void Working(CallBack callBack)  
  18.         {  
  19.             for(int i=0;i<10000;i++)  
  20.             {  
  21.                 //处理事件  
  22.                 callBack(i);  
  23.             }  
  24.         }  
  25. }  

我们把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的类型。

[csharp]  view plain  copy
  1. class Program  
  2.     {  
  3.         public delegate void Handler(object obj);  
  4.   
  5.         public static void GetMessage(object message)  
  6.         {  
  7.             if (message is string)  
  8.                 Console.WriteLine("His name is : " + message.ToString());  
  9.             if (message is int)  
  10.                 Console.WriteLine("His age is : " + message.ToString());  
  11.         }  
  12.   
  13.         static void Main(string[] args)  
  14.         {  
  15.             Handler handler = new Handler(GetMessage);  
  16.             handler(29);  
  17.             Console.ReadKey();  
  18.         }  
  19.    }  

2.泛型委托

对于以上的方法,如果都以object作为参数,每次都要进行拆箱操作是非常消耗性能的,过程也很繁琐。

因此这里引入了泛型委托的概念。

[csharp]  view plain  copy
  1. class Program  
  2.   {  
  3.       public delegate void Handler<T>(T obj);  
  4.       static void Main(string[] args)  
  5.       {  
  6.           Handler<int> handler1 = new Handler<int>(getSquare);  
  7.           Handler<string> handler2 = new Handler<string>(sayHi);  
  8.           handler1(2);  
  9.           handler2("Wix");  
  10.           Console.ReadKey();  
  11.       }  
  12.       static void getSquare(int a)  
  13.       {  
  14.           Console.WriteLine(a * a);  
  15.       }  
  16.       static void sayHi(string name)  
  17.       {  
  18.             
  19.           Console.WriteLine("Hi,"+name);  
  20.       }  

输出结果如图:

什么时候使用泛型委托?

如果你想绑定多个不同类型参数方法的话可以使用泛型委托,而且不需要用is进行类型判断。这样我们就可以不用定义多个不同参数类型的委托了。

什么是事件?

简单的来说,事件的由来是为了保证系统的封装性。

上面的代码可以看到委托的声明都是public,这使得外界可以直接进行调用或赋值操作。

如果设置成private,我们需要添加AddHandlerRemoveHandler方法(+=-=),就有如getset方法,很麻烦。

所以事件这个概念由此而生。

[csharp]  view plain  copy
  1. public class EventTest  
  2.  {  
  3.      public delegate void MyDelegate();  
  4.      public event MyDelegate MyEvent;  
  5.   }  


*事件对应的变量成员将会被视为 private 变量

如果不明白,可以想象事件是委托的容器,可以保证委托的封装性。

事件能通过+=-=两个方式注册或者注销对其处理的方法


[csharp]  view plain  copy
  1. public delegate void MyDelegate(string name);  
  2.   
  3.     public class PersonManager  
  4.     {  
  5.         public event MyDelegate MyEvent;  
  6.   
  7.         //执行事件  
  8.         public void Execute(string name)  
  9.         {  
  10.             if (MyEvent != null)  
  11.                 MyEvent(name);  
  12.         }  
  13.     }  
  14.   
  15.     class Program  
  16.     {  
  17.         static void Main(string[] args)  
  18.         {  
  19.             PersonManager personManager = new PersonManager();  
  20.             //绑定事件处理方法  
  21.             personManager.MyEvent += new MyDelegate(GetName);  
  22.             personManager.Execute("Leslie");  
  23.             Console.ReadKey();  
  24.         }  
  25.   
  26.         public static void GetName(string name)  
  27.         {  
  28.             Console.WriteLine("My name is " + name);  
  29.         }  
  30.     }  

我们也可以直接绑定方法

  personManager.MyEvent += GetName;

或者绑定匿名方法

 personManager.MyEvent += delegate(string name){

                 Console.WriteLine("My name is "+name);

             };

总结:事件就是一个特殊的委托。



什么时候用事件?

事件可以把逻辑流程拆分成几个阶段,在游戏中,如单位生产事触发某个事件,单位死亡时触发某个事件。

下面是模拟unity中的代码例子:

[csharp]  view plain  copy
  1. class UnitHandler  
  2.    {  
  3.   
  4.        public delegate void UnitEventHandler(GameObject unit);  
  5.        public static event UnitEventHandler onUnitSpawn;  
  6.        public static event UnitEventHandler onUnitDestroy;  
  7.        public static void NewUnitCreated(GameObject unit)  
  8.        {  
  9.            if (onUnitSpawn != null)  
  10.                onUnitSpawn(unit);  
  11.        }  
  12.        public static void UnitDead(GameObject unit)  
  13.        {  
  14.            if(onUnitDestroy!=null)  
  15.            onUnitDestroy(unit);  
  16.         
  17.        }  
  18.   
  19.   
  20. class UnitManager  
  21.    {  
  22.     public void OnEnabled()  
  23.        {  
  24.            UnitHandler.onUnitSpawn += this.NewUnitCreated;  
  25.        }  
  26.        public void NewUnitCreated(GameObject unit)  
  27.        {  
  28.            Console.WriteLine("unit created");  
  29.            Console.ReadKey();  
  30.        }  
  31.   
  32.      public  void OnDisable()  
  33.        {  
  34.            UnitHandler.onUnitSpawn -= this.NewUnitCreated;  
  35.        }  
  36.    }  
  37.   
  38.  class Program  
  39.    {  
  40.        static void Main(string[] args)  
  41.        {  
  42.            UnitManager um = new UnitManager();  
  43.            um.OnEnabled();  
  44.            UnitHandler.NewUnitCreated(new GameObject());  
  45.            um.OnDisable();  
  46.            UnitHandler.NewUnitCreated(new GameObject());  
  47.        }  
  48.    }  

*这里的GameObject是一个空类

运行结果为:

unit created


事件可以在某件事件发生时让一个对象通知另一个对象,可以理解为监听某个事件,然后在特定条件下触发。

下面再看一个例子:

在我们创建一个事件之前,我们需要一个委托,而一般标准的委托声明如下:

    public delegate void EventHandler(object sender, System.EventArgs e);

第一个形参object sender定义了对象来源,第二个形参放的是继承自System.EventArgs的类,一般上这个类包含了事件的详细信息。

例子:

    class ButtonEventArgs:EventArgs

    {

        public string time;

}

在这里我们不需要传递什么事件信息,所以我们用基类EventArgs就好。

[csharp]  view plain  copy
  1. public delegate void EventHandler(object sender, System.EventArgs e);  
  2.   
  3.    class Publisher  
  4.    {  
  5.        public event EventHandler Added; //定义发生事件  
  6.   
  7.   
  8.        protected virtual void OnAdded(System.EventArgs e) //当事件发生中触发方法  
  9.        {  
  10.            if(Added!=null)  
  11.            {  
  12.                Added(this, e);  
  13.            }  
  14.        }  
  15.        public void Add(object value) //触发事件的方法  
  16.        {  
  17.     
  18.            OnAdded(System.EventArgs.Empty);  
  19.        }  
  20.   
  21.   
  22.   
  23.   
  24.    class Subscriber  
  25.    {  
  26.        void AddedEventHandler(object sender,System.EventArgs e)  
  27.        {  
  28.            System.Console.WriteLine("AddEvent occured");  
  29.        }  
  30.          
  31.        static void Main()  
  32.        {  
  33.            Subscriber s = new Subscriber();  
  34.            Publisher p = new Publisher();  
  35.            p.Added += s.AddedEventHandler;  
  36. .Add(10);  
  37.        }  


事件的使用步骤如下:

存放事件的类

1.定义事件

2.触发事件的方法(protected)

3.间接触发事件的方法(public)

触发事件的类

1.定义方法

2.注册方法

3.触发方法

再来看最后一个例子:


[csharp]  view plain  copy
  1. public class MyEventArgs : EventArgs  
  2.     {  
  3.         private string args;  
  4.   
  5.         public MyEventArgs(string message)  
  6.         {  
  7.             args = message;  
  8.         }  
  9.   
  10.         public string Message  
  11.         {  
  12.             get { return args; }  
  13.             set { args = value; }  
  14.         }  
  15.     }  
  16.   
  17.     public class EventManager  
  18.     {  
  19.         public event EventHandler<MyEventArgs> myEvent;  
  20.   
  21.         public void Execute(string message)  
  22.         {  
  23.             if (myEvent != null)  
  24.                 myEvent(thisnew MyEventArgs(message));  
  25.         }  
  26.     }  
  27.   
  28.     class Program  
  29.     {  
  30.         static void Main(string[] args)  
  31.         {  
  32.             EventManager eventManager = new EventManager();  
  33.             eventManager.myEvent += new EventHandler<MyEventArgs>(ShowMessage);  
  34.             eventManager.Execute("How are you!");  
  35.             Console.ReadKey();  
  36.         }  
  37.   
  38.         public static void ShowMessage(object obj,MyEventArgs e)  
  39.         {  
  40.             Console.WriteLine(e.Message);  
  41.         }  
  42.     }  

这里我们使用了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


版权声明:本文为博主原创文章,未经博主允许不得转载。 http://blog.csdn.net/Wiiix/article/details/51463977


猜你喜欢

转载自blog.csdn.net/u013751758/article/details/79565792