C#基础系列-委托与事件

一、前言

  C#的委托和事件是比较难以理解的内容,主要是没有理解什么是委托和事件,使用的主要场景是什么,解决了什么问题。大多数时候记住的可能是模糊的定义和各种简短的概要,没有全面的了解而是浅尝辄止。另外.NET提供的各种语法糖让原本就不理解的内容蒙上了神秘的色彩。所以从定义、场景、示例三个方面探究一下。

二、委托

  什么是委托,委托是寻址方法的.NET版本,.NET版本是相对于C或者C++中的函数指针,委托可以看成是.NET的函数指针。区别在于C或者C++的函数指针是指向内存的位置,其本质是地址所以无法判断其实际的指向内容是什么,体现其是类型不安全,而委托是类型安全,因为委托是一个类和普通类一样必须要先定义类,通过类创建实例,而定义的类方式是必须指定方法的签名和返回类型,体现其是类型安全。委托类可以理解成方法的签名和返回类型的指定名称。

  委托使用场景,方法中定义的参数一般是数据类型,现在可以通过委托将方法进行“包装”成普通的参数进行传递。在使用中比如1System.Threading.Tasks中启动线程和任务,然后执行相应的方法,则必须为线程和任务传递执行的方法,通过委托(实例)传递参数;在对象数组中进行排序,对象排序规则要自定义,这时可以传递委托(实例)定义排序的方式;事件,基于委托实现事件。

    /// <summary>
    /// 定义委托类型
    /// </summary>
    /// <param name="str"></param>
    /// <returns></returns>
    public delegate string SetStrDelegate(string str);
    class Program
    {
        static void Main(string[] args)
        {
            // 创建委托实例(构造函数参数是与定义类一致的签名的方法名称)
            SetStrDelegate setStrDelegate = new SetStrDelegate(SetStrMethod);
            //委托实例作为参数形式传入
            Console.WriteLine(GetStrMethod(setStrDelegate,"委托"));
            Console.ReadKey();
        }
        /// <summary>
        /// 创建委托实例的参数(委托作为“地址”指向的方法)
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string SetStrMethod(string str)
        {
            return string.Format("SetStrMethod({0})", str);
        }

        /// <summary>
        /// 使用委托实例的方法
        /// </summary>
        /// <param name="setStrDelegate"></param>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string GetStrMethod(SetStrDelegate setStrDelegate,string str)
        {
            // 通过委托执行方法(或者委托实例调用Invoke()方法等同实现)
            return setStrDelegate(str);
        }
  }
    // Action<T>和Func<T>泛型委托
    // Action<T>表示引用一个void返回类型的方法,这个委托存在不同的变体,可以传递至多16个不同类型参数
    // Func<T>表示引用一个带返回类型的方法,Func<out TResult>,这个委托存在不同变体,可以传递至多16个不同类型参数,最后一个参数是返回类型
    // 使用这两个泛型委托简化了委托类型的定义,可以很方便的使用委托,满足所有委托类型的要求
    class Program
    {
        static void Main(string[] args)
        {
            // 创建Action<T>委托实例,参数是一个string,返回类型是void
            Action<string> action = new Action<string>(SetStrActionMethod);
            // 使用委托实例(可以理解委托方法SetStrActionMethod的执行“委托”给action)
            action("委托");
            SetStrAction(action,"委托");
            // 创建Func<T>委托实例,参数是一个string,返回类型是string
            Func<string, string> func = new Func<string, string>(SetStrFuncMethod);
            // 使用委托实例
            Console.WriteLine(func("委托"));
            var returnStr = SetStrFunc(func, "委托");
            Console.WriteLine(returnStr);
            Console.ReadKey();
        }

        /// <summary>
        /// 创建Action<T>委托实例的参数(委托作为“地址”指向的方法)
        /// </summary>
        /// <param name="str"></param>
        public static void SetStrActionMethod(string str)
        {
            Console.WriteLine(string.Format("SetStrActionMethod({0})", str));
        }

        /// <summary>
        /// 使用委托实例的方法
        /// </summary>
        /// <param name="action"></param>
        /// <param name="str"></param>
        public static void SetStrAction(Action<string> action, string str)
        {
            action(str);
        }

        /// <summary>
        /// 创建Func<T>委托实例的参数(委托作为“地址”指向的方法)
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string SetStrFuncMethod(string str)
        {
            return string.Format("SetStrFuncMethod({0})", str);
        }

        /// <summary>
        /// 使用委托实例的方法
        /// </summary>
        /// <param name="func"></param>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string SetStrFunc(Func<string,string> func,string str)
        {
            return func(str);
        }
  }
    /// <summary>
    /// 匿名方法和lambda表达式与委托,使用委托必须要一个方法存在,而且方法的签名和委托类的定义一致
    /// 通过使用匿名方法和lambda表达式简化这个过程
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            // 匿名方法、定义并且创建委托实例
            Func<string, string> SetStr = delegate (string str)
            {
                return string.Format("匿名方法{0}", str);
            };
            // 使用委托实例
            Console.WriteLine( SetStr("委托"));
            // lambda表达、定义并且创建委托实例
            Func<string, string> SetStrLambda = str =>
            {
                return string.Format("Lambda表达式{0}", str);
            };
            Console.WriteLine(SetStrLambda("委托"));
            Console.ReadKey();
        }
    }

三、事件

  什么是事件,事件基于委托,为委托提供了发布订阅的机制。事件一直是很困惑一块内容,虽然在winform和webform中使用事件,JavaScript中也有事件,但仅在使用阶段,控件的拖拽,创建事件代码,然后在事件中编辑业务逻辑内容,不知道事件具体机制和为什么使用事件。学习ASP.NET MVC的请求管道的19个事件和事件相关的HttpModule时候,通过自定义Module绑定事件可以提供实际业务场景的灵活处理方式。所以想到事件在这个管道使用中带来的扩展,还有在系统架构中使用事件总线(EventBus)对业务在代码实现的解耦作用,从中体会到发布订阅机制的魅力。

  定义一个事件,关注和受事件影响者来订阅事件,而发布者在一定条件触发事件,订阅者监听事件并且执行业务。让发布者不关心订阅者如何处理业务,订阅者也不关心发布者内部如何发布事件,定义好事件的含义,通过事件串联两者。

  事件的神秘性在于C#对其进行了很好的封装,使用者没有体会到具体是怎么实现的,只要简单定义事件,订阅事件,发布事件就能轻松完成这个机制。其实在Windows中有消息处理机制,比如鼠标的点击、键盘的按键输入、窗体的变化等都是通过消息处理机制定义好的,这些都是在底层已经开发封装好的功能,C#的事件(Event)就是基于windows消息处理机制。

  事件与委托的关系,事件表示的是一个动作,一个事情、至于如何实现事件和事件发生后如何处理,就要使用委托。比如租房是一个事件,那么中介就是委托,只要告诉中介租房的诉求,中介依据要求查找方案租赁房子,完成这个事件;鼠标发生了点击事件,至于点击事件应该如何处理则由关注或注册了这个事件的委托处理程序进行处理,完成这个事件。

  事件的使用场景,应用程序中winform和webform的事件驱动,ASP.NET MVC的管道模式的事件,系统架构中事件总线的解耦。

// 定义委托,为类B的事件处理方法定义委托类型(方法的签名和参数)
    public delegate void handler(object sender, MyEventArgs args);
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            B b = new B(a); // 构造函数注入对象(实例)
            // 定义事件参数
            MyEventArgs arg = new MyEventArgs()
            {
                id = 1,
                name =  "事件源参数",
                title = "事件源类A发布事件"
            };

            // 事件源类A创建的实例a通过方法Happen发布事件
            a.Happen(arg);
            Console.ReadKey();
        }
    }

    // 类A事件源
    public class A
    {
        // 定义事件,订阅事件的委托
        public event handler Event;
// 发布事件方法 public void Happen(MyEventArgs args) { if (Event != null) { Event(this,args); } } } // 类B事件处理 public class B { public B(A a) { // 创建委托实例 handler handler = new handler(OnHandler); // 将委托实例绑定到事件上,进行事件的订阅(注册)当事件发生时候,通过委托调用委托实例化的方法(OnHandler) a.Event += handler; } // 事件处理方法与委托类型的具有一致的签名 public void OnHandler(object sender, MyEventArgs args) { Console.WriteLine("id=>{0},name=>{1},title=>{2}", args.id, args.name, args.title); } } // 事件传入的参数(继承基类) public class MyEventArgs : EventArgs { public int id { get; set; } public string name { get; set; } public string title { get; set; } }

四、总结

  委托的使用是使方法作为参数的形式传入方法,在方法中直接调用方法也可以实现一样的效果,为什么还要使用委托这个特性?在一定的业务场景下,如果方法是变化则不容易进行扩展,使用委托则可以提供灵活性,通过传入不同的方法,实现不同的业务。委托为其他特性提供了基础,最明显就是事件。

  ps:个人总结,欢迎讨论,任何特性只有在使用中,才能体会其魅力。

posted @ 2020-12-20 17:44  tuqunfu  阅读(314)  评论(0编辑  收藏  举报