C# 委托(Delegate)和事件(Event)总结

目录

一,什么是委托?

1 .1官方关于委托的概述

1.2 通俗解释

二,怎么使用委托?

2.1 委托的定义(声明)

2.2 委托的实例化

2.3 委托的调用

2.4 委托的使用实例

三,委托实现嵌套中间件

3.1 委托+Lambad表达式=简单嵌套实现

3.2 委托+特性+反射=花式嵌套实现

四,泛型委托

4.1 自定义泛型委托

4.2 框架内置委托(泛型委托Action/Func)

(1)Action

(2) Func

五,多播委托和事件

六,WinForm中按钮点击事件解析

6.1 制定事件的标准流程 


一,什么是委托?

1 .1官方关于委托的概述

(1) 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。
(2) 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 委托可以链接在一起,一次性调用多个方法
(3) 你可以通过委托实例调用方法。
(4) 委托用于将方法作为参数传递给其他方法,可用于定义回调方法
(5) 可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。该方法可以是静态方法,也可以是实例方法。 此灵活性意味着你可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。
(6) 委托类型派生自 .NET 中的 Delegate 类。 委托类型是密封的,它们不能派生自 Delegate,也不能从其派生出自定义类

1.2 通俗解释

  从字面意思上说,“委托”就是一件事,我们不亲自去做,而是把这件事交给其他人帮忙做;但从编程语言的角度来说,编程语法机制也是一种C#语言对现实世界逻辑的抽象。
  委托可以看做出C++中指针的升级版,它的 本质是一个类。其中定义了方式的类型,使得可以将方法作为另一个方法的参数进行传递。这种写法可以避免程序中存在大量的If else/switch语句,同时提高了程序的扩展性。

二,怎么使用委托?

2.1 委托的定义(声明)

对于委托的定义主要有以下几种:

    //1.无参数无返回值在类外部委托
    public delegate void NoReturnNoParaOutClass();
    class CustomDelegate
    {
        //2.无参数无返回值在类内部委托
        public delegate void NoReturnNoPara();

        //3.有参数无返回值委托
        public delegate void NoReturnWithPara(int x, int y);

        //4.无参数有返回值委托
        public delegate int NoParaWithReturn();

        //5.有参数有返回值委托
        public delegate int WithParaWithReturn(int x, int y);
    }

由于委托是继承自特殊类MulticastDelegate,因此在使用时需要实例化:通过New来实例化,且要求传递一个和这个委托的参数和返回值完全匹配的方法。

2.2 委托的实例化

实例化方式主要有三种:

  1. 采用New关键字
  2. 使用语法糖(直接指向方法,简化New)
  3. 指向类型一致的lambad表达式。
        public delegate int WithParaWithReturn(int x, int y);
        //实例化方式1
        WithParaWithReturn withParaWithReturn = new WithParaWithReturn(method);
        //实例化方式2
        WithParaWithReturn withParaWithReturn1 = method; //采用语法糖
        //实例化方式3
        WithParaWithReturn withParaWithReturn2 = (x,y)=> { return 0; };
        //有返回值有参数的方法
        public static int method(int a,int b)
        {
            return a + b;
        }

2.3 委托的调用

  1.  直接调用委托的实例对象,如:withParaWithReturn1(3,2);

  2. 使用Invoke(),如:withParaWithReturn1.Invoke(3,2);

  3. 如果希望异步调用,则用BeginInvoke调用。(即另开一个线程执行)

  4. EndInvoke等待BeginInvoke执行完成后再执行。

2.4 委托的使用实例

    //【1】申明委托
    public class DelegateTest
    {
        // 1.无参数无返回值委托
        public delegate void NoReturnNoPara();

        // 2.有参数无返回值委托
        public delegate void NoReturnWithPara(int x, int y);

        // 3.无参数有返回值的委托
        public delegate int WithReturnNoPara();

        // 4.带参数带返回值的委托
        public delegate int WithReturnWithPara(out int x, out int y);
    }
 class Program
    {
        //【2】定义委托相关的方法
        private static void NoReturnNoParaMethod()
        {
            Console.WriteLine("无参数,无返回值的方法");
        }

        private static void NoReturnWithParaMethod(int s, int t)
        {
            Console.WriteLine("有参数,无返回值的方法");
        }

        static void Main(string[] args)
        {
            //【3】实例化委托
            //使用new 实例
            DelegateTest.NoReturnNoPara noReturnNoPara= new DelegateTest.NoReturnNoPara(NoReturnNoParaMethod);

            //使用赋值的方式实例
            DelegateTest.NoReturnWithPara noReturnWithPara = NoReturnWithParaMethod;

            //使用匿名委托实例
            DelegateTest.WithReturnNoPara withReturnNoPara = delegate() 
            {
                Console.WriteLine("无参数,有返回值的方法");
                return default(int);
            };

            //使用lambda 匿名方法实例
            DelegateTest.WithReturnWithPara WithReturnWithPara = (out int x, out int y) =>
            {
                x = 1;
                y = 2;
                Console.WriteLine("有参数,有返回值的方法");
                return x + y;
            };

            //【4】调用委托
            //使用委托变量调用
            noReturnNoPara();

            //使用invoke调用
            //【Invoke】执行方法,如果委托定义没有参数,则invoke也没有参数,委托没有返回值,则invoke也没有返回值
            noReturnNoPara.Invoke();

            int result= withReturnNoPara.Invoke();//调用有返回值,无参数的委托

            int x1, y1;
            int result2 = WithReturnWithPara.Invoke(out x1,out y1);//调用有返回值,有参数的委托

            //使用BeginInvoke
            //【BeginInvoke】开启一个线程去执行委托,NetCore不支持,NetFamework支持  NetCore有更好的多线程功能来支持实现类似功能
            noReturnWithPara.BeginInvoke(1,2,null,null);
            //【EndInvoke等待BeginInvoke方法执行完成后再执行EndInvoke后面的代码】
            //noReturnWithPara.EndInvoke();
            Console.ReadLine();
        }
    }

三,委托实现嵌套中间件

3.1 委托+Lambad表达式=简单嵌套实现

  • (1)声明一个委托,并定义一个普通类CustomClass,用于实现核心的业务逻辑。
    public delegate void ShowDelegate();
    class CustomClass
    {
        public void Method()
        {
            Console.WriteLine("核心业务逻辑运行");
        }
    }
  • 主程序中创建多个委托实例,并通过Lambad表达式迭代调用。
        static void Main(string[] args)
        {
            ShowDelegate showMthod1 = new ShowDelegate(() =>
              {
                  Console.WriteLine("showMthod1执行前");
                  new CustomClass().Method();
                  Console.WriteLine("showMthod1执行后");

              });
            ShowDelegate showMthod2 = new ShowDelegate(() =>
            {
                Console.WriteLine("showMthod2执行前");
                showMthod1.Invoke();
                Console.WriteLine("showMthod2执行后");
            });
            ShowDelegate showMthod3 = new ShowDelegate(() =>
            {
                Console.WriteLine("showMthod3执行前");
                showMthod2.Invoke();
                Console.WriteLine("showMthod3执行后");
            });
            showMthod3.Invoke();
        }

最后效果:

3.2 委托+特性+反射=花式嵌套实现

  • (1)声明委托并定义抽象特性以及子类特性(子类用于实现嵌套业务)
    public delegate void ShowDelegate();
    //定义抽象特性
    public abstract class AbstractMethodAttribute:Attribute
    {
        public abstract ShowDelegate Do(ShowDelegate action);
    }
    //定义子类特性实现Log业务
    public class DelegateLogAttribute:AbstractMethodAttribute
    {
        //LOG业务嵌套
        public override ShowDelegate Do(ShowDelegate action)
        {
            ShowDelegate actionResult = new ShowDelegate(() =>
              {
                  Console.WriteLine("在执行LOG之前");
                  action.Invoke();
                  Console.WriteLine("在执行LOG之后");
              });
            return actionResult;
        }
    }
    //定义子类特性实现Error业务
    public class DelegateErrorAttribute : AbstractMethodAttribute
    {
        //Error业务嵌套
        public override ShowDelegate Do(ShowDelegate action)
        {
            ShowDelegate actionResult = new ShowDelegate(() =>
            {
                Console.WriteLine("在执行ERROR之前");
                action.Invoke();
                Console.WriteLine("在执行ERROR之后");
            });
            return actionResult;
        }
    }
  • (2)定义一个类用于实现核心业务逻辑,并标记特性
    public class CustomClass
    {
         [DelegateLog]
        [DelegateError]
        public void Method()
        {
            Console.WriteLine("花式嵌套业务核心");
        }
    }
  • (3)主程序通过反射调用特性
  static void Main(string[] args)
        {
            CustomClass customClass = new CustomClass();
            Type type = customClass.GetType();
            MethodInfo methodInfo = type.GetMethod("Method");
            //给委托赋值,初始委托方法
            ShowDelegate showMethod = new ShowDelegate(() =>
            {
                customClass.Method();
            });
            //判断是否定义特性对每个特性进行执行
            //继承自父类的特性都算
            if (methodInfo.IsDefined(typeof(AbstractMethodAttribute), true))
            {
                //Reverse越靠近方法越先执行
                foreach (AbstractMethodAttribute attribute in methodInfo.GetCustomAttributes().Reverse())
                {
                    //把初始方法传入,返回封装好的委托再作为下一个参数传入
                    showMethod = attribute.Do(showMethod);
                }
            }
            //执行委托
            showMethod.Invoke();
        }

最后效果:

🧡🧡🧡 这种写的好处在哪?🧡🧡🧡

如果我们增加一个最新的业务逻辑,比如在LOG业务之前进行CHECK检查,只需要再写一个CHECK特性继承自抽象特性,然后在实现核心业务逻辑类里进行标记即可。

 最终效果:

在执行CHECK之前
在执行LOG之前
在执行ERROR之前
花式嵌套业务核心
在执行ERROR之后
在执行LOG之后
在执行CHECK之后

四,泛型委托

4.1 自定义泛型委托

    //【1】定义了具有两个泛型参数类型的委托
    public delegate void CustomDelegate<T, V>(T t, V v);

    public class TestCustomDelegate
    {
        //【2】申明委托变量,这时需指明数据类型
        CustomDelegate<string, string> customDelegate;
        //【3】编写与委托对应的方法
        public void TestMethod(string a,string b)
        {
            Console.WriteLine($"{a}拼接{b}");
        }

        public void Result()
        {
            //【4】实例化
            customDelegate = TestMethod;
            customDelegate = new CustomDelegate<string, string>(TestMethod);
            customDelegate = delegate (string a1, string b1) { Console.WriteLine("delegate匿名方法也可以"); };
            customDelegate = (string a2, string b2) => { Console.WriteLine("使用lambda匿名方法"); };
            //【5】执行委托
            customDelegate("test1","test2");
            customDelegate.Invoke("test1", "test2");
        }
    }

由于单独定义委托和事件,比较繁琐,而且比较冗余,因此C#2.0提供了Action 和Func两个泛型委托,不用单独申明,拿来就可以用。

4.2 框架内置委托(泛型委托Action/Func)

(1)Action

  • Action 表示无参,无返回值的委托
  • Action<int,string> 表示有参,无返回值的泛型委托,最多可入参16个
  • 使用Action 就可以囊括所有无返回值委托,可以说Action是对无返回值委托的进一步包装

Action本身就代表无返回,无参数,因此将void 和 ()一省略,只剩下Action action #Action 为我们编程提供了方便,哪里需要就在哪里直接定义使用,不需要单独去定义

    public class ActionTest
    {
        public Action action;
        public void SendMsg()
        {
            Console.WriteLine("消息完成发送");
        }
        public void Test()
        {
           //实例化方式有四种,如下
            action = SendMsg;
            action = new Action(SendMsg);
            action = delegate(){ Console.WriteLine("delegate 匿名方法"); };
            action = () => { Console.WriteLine("lambda 匿名方法"); };
            //调用方式
			action();
            action.Invoke();
        }
    }

(2) Func

  • Func 表示有返回值的委托(必须有返回值)
  • Func可以无参数,也可以有参数,最多16个参数,最后一个表示返回值且只有一个
    public class FuncTest
    {
        public Func<int,int> func;
        public int DoubleNumber(int number)
        {
            Console.WriteLine("计算完成");
            return number * 2;
        }
        public void Test()
        {
            //实例化有以下4种方式
            func = DoubleNumber;
            func = new Func<int, int>(DoubleNumber);
            func = delegate (int a) 
            {
                Console.WriteLine("delegate 匿名方法");
                return a * 2 ;
            };
            func = (int b) => 
            {
                Console.WriteLine("lambda 匿名方法");
                return b * 2;
            };

            //调用
            int result = func(10);
            int result2 = func.Invoke(10);
        }
    }

五,多播委托和事件

  • 委托都是继承自MulticastDelegate(多播委托),定义的所有的委托都是多播委托
  • 可以通过+=把多个方法添加到这个委托中,形成一个方法的执行链,执行委托的时候,按照添加方法的顺序,依次去执行方法
  • 可以通过-=移除方法,是从后往前,逐个匹配,如果匹配不到,就不做任何操作,如果匹配到,就把当前这个移除,且停止去继续往后匹配
  • action.BeginInvoke();会开启一个新的线程 去执行委托,注册有多个方法的委托,不能使用BeginInvoke
  • 注册有多个方法的委托想要开启新线程去执行委托,可以通过action.GetInvocationList()获取到所有的委托,然后循环,每个方法执行的时候可以BeginInvoke

现在有一个需求:猫叫之后引发一系列的动作(小孩哭,大人醒,打开灯)。

一般方式:封装一个方法,调用一系列动作。

public void Miao()
{
    Console.WriteLine("{0} Miao", this.GetType().Name);
    new Baby().Cry(); //小孩哭了
    new Men().awake();//大人醒了
    new light().Open(); //灯打开
}

整个代码职责不单一,依赖于其他的类太多,代码不稳定,任何一个类的修改,都有可能会影响到这只猫

基于面向对象的观察者模式:引发的动作注册到方法列表中去

(1)定义一个接口,所有引发的动作类都继承该接口。

  public interface IObject
    {
        void Invoke();
    }
    public class Men:IObject
    {
        public void Invoke()
        {
            Console.WriteLine("大人醒来");
        }
    }
    public class Baby : IObject
    {
        public void Invoke()
        {
            Console.WriteLine("小孩哭了");
        }
    }
    public class light : IObject
    {
        public void Invoke()
        {
            Console.WriteLine("灯被打开");
        }
    }

(2)定义猫cat类,通过list列表获取引发的动作。

public class cat
    {
        public List<IObject> objectList = new List<IObject>();
        public void MiaoObsever()
        {
            Console.WriteLine("喵咪叫了“喵~");
            if (objectList.Count>0)
            {
                foreach (var item in objectList)
                {
                    item.Invoke();
                }
            }        
        }
    }

(3)主程序调用

static void Main(string[] args)
        {
            cat cat = new cat();
            cat.objectList.Add(new Baby());// 小孩哭了
            cat.objectList.Add(new Men()); // 大人醒来
            cat.objectList.Add(new light()); // 灯被打开
            cat.MiaoObsever();//执行
        }

可以看的,猫只是执行方法列表,方法列表的注册交给第三方,不在猫的内部。 

基于委托的观察者模式:引发的动作注册到多播委托中去

    public class cat
    {
        public Action MiaoDelegateHandler = null;
        public void MiaoDelegate()
        {
            Console.WriteLine("喵咪叫了“喵~");
            MiaoDelegateHandler?.Invoke(); //?. 如果不为null ,就执行后面的动作 
        }
    }  
    //程序调用          
     static void Main(string[] args)
        {
            cat cat = new cat();
            cat.MiaoDelegateHandler += new Baby().Cry; // 小孩哭了
            cat.MiaoDelegateHandler += new Men().awake; // 大人醒来
            cat.MiaoDelegateHandler += new light().Open; // 灯被打开
            cat.MiaoDelegate();//执行

        }

可以看的,猫cat类只是执行委托的方法链,方法链注册交个第三方,不在猫内部 

基于事件的观察者模式: 引发的动作注册到事件中去

和基于委托的观察者模式类似,只需要在 public Action MiaoDelegateHandler = null;中加入event关键字。

🧡🧡🧡 既然多播委托和事件写法区别只在于少一个event关键字,事件的作用是什么?🧡🧡🧡

 在上面基于多播委托的观察者模式实例中,我们可以在方法内部通过cat的委托实例对象直接调用内部的委托MiaoEventHandler。

 也就是说,在外部程序中,不用通过猫叫就可以触发一系列动作,违背了设计需求。(要求是只能是通过猫叫触发!)

因此通过对event关键字将委托声明为事件,事件只能在当前类被访问,使得子类和类外部均不能执行类中的事件方法

六,WinForm中按钮点击事件解析

 以WinForm中最常见的Button按钮绑定的Click事件为例,我们在页面上添加登录按钮双击生成了一个方法,运行起来,点击按钮,触发这个方法,这个过程是怎么完成的?

  • 首先,按钮是一个Button类,继承自Control类,在Control类中有一个Click事件。
 EventHandler(object? sender, EventArgs e)
  • MyWinForm构造函数函数中有一个InitializeComponent方法,在InitializeComponent方法中初始化Button按钮实例,Button的实例中的Click事件+=一个动作btnLogin_Click方法。
  • 然后点击按钮,触发事件,并执行事件绑定的方法,也就是btnLogin_Click方法。

添加按钮,注册事件

namespace MyWinform
{
    partial class MyWinForm
    {
        /// <summary>
        ///  Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        ///  Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        ///  Required method for Designer support - do not modify
        ///  the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.btnLogin = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // btnLogin
            // 
            this.btnLogin.Location = new System.Drawing.Point(78, 59);
            this.btnLogin.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            this.btnLogin.Name = "btnLogin";
            this.btnLogin.Size = new System.Drawing.Size(73, 25);
            this.btnLogin.TabIndex = 0;
            this.btnLogin.Text = "登录";
            this.btnLogin.UseVisualStyleBackColor = true;
            this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
            // 
            // MyWinForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(312, 213);
            this.Controls.Add(this.btnLogin);
            this.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            this.Name = "MyWinForm";
            this.Text = "MyWinForm";
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Button btnLogin;
    }
}

添加执行逻辑

using System;
using System.Windows.Forms;

namespace MyWinform
{
    public partial class MyWinForm : Form
    {
        public MyWinForm()
        {
            InitializeComponent();
        }

        private void btnLogin_Click(object sender, EventArgs e)
        {
            MessageBox.Show("触发了点击事件");
        }
    }
}

 运行效果:

6.1 制定事件的标准流程 

  1. 发布者发布事件

  2. 订阅者订阅事件

  3. 触发事件

(1)定义发布者

// 发布者:对外发布事件;触发事件;
public class Publisher
{
    //发布事件
    public event EventHandler Publish;

    //发布者触发事件
    public void EventAction()
    {
        Console.WriteLine("触发事件");
        Publish?.Invoke(null,null);
    }
}

(2)定义订阅者 

// 订阅者:对发布者发布的事情关注
public class Observer1
{
    // 订阅者1的行为
    public void Action1(object sender, EventArgs e)
    {            
        Console.WriteLine("订阅者1的行为");
    }
}

// 订阅者:对发布者发布的事情关注
public class Observer2
{
    // 订阅者2的行为
    public void Action2(object sender, EventArgs e)
    {
        Console.WriteLine("订阅者2的行为");
    }
}

(3)发布订阅触发事件

//初始化发布者
Publisher publisher = new Publisher();
//初始化订阅者1
Observer1 observer1 = new Observer1();
//初始化订阅者2
Observer2 observer2 = new Observer2();
//订阅者订阅事件
publisher.Publish += observer1.Action1;
publisher.Publish += observer2.Action2;
//触发事件
publisher.EventAction();

 运行结果:

触发事件
订阅者1的行为
订阅者2的行为

  • 6
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值