委托基础详解

 /*委托是一个类型安全的对象
        * 它指向程序中另一个以后被调用的方法或多个方法。委托类型包含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("小汽车上路。。。。
");

            //首先:创建一个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("
***报警信息*****");
            Console.WriteLine("信息提示 => {0}", msg);
            Console.WriteLine("
********");
        }

委托协变

  //委托协变,你有没有想过,如果委托类型指向返回自定义类类型的方法?
            /*
             * 说白了协变和逆变也就是,创建一个委托,指向多个方法,方法的参数是存在继承关系的对象,之间的一个转换
             
             */

定义两个类。必须有继承关系

 

  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("小汽车上路。。。。
");
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("
***报警信息*****");
271             Console.WriteLine("信息提示 => {0}", msg);
272             Console.WriteLine("
********");
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 }
原文地址:https://www.cnblogs.com/nsky/p/4414138.html