26、C#委托与事件

一、unity里使用委托常见场景

1、在绑定按钮事件的使用场景里,如果想批量地绑定按钮事件,同时想通过绑定事件传递被点击按钮的名字,那么这时候可以通过委托的方式,这样可以将按钮的名字传递给绑定事件处理方法。

btnTake.onClick.AddListener(() =>
                {
                    ClickTakeBtn(go.name);
                });



 private void ClickTakeBtn(string name)
    {
        ...
    }

2、作为事件分发中心。比如网络数据或者外部的消息数据到达了本地后,往往会做一个消息处理器,然后处理器的做法就是把数据做解析,然后转成对应的事件,最后把事件分发出去,转到各个模块。

二、委托的本质

1、定义。C#中的委托是一个类型,它描述了一个方法的签名,即方法的参数类型和返回类型。委托可以看作是一个指向方法的引用,使得我们可以像使用函数指针一样调用这些方法。我们习惯于把数据作为参数传递给方法,所以,给方法传递另一个方法听起来有点奇怪。而有时某个方法执行的操作并不是针对数据进行的,而是要对另一个方法进行操作。更麻烦的是,在编译时我们不知道第二个方法是什么,这个信息只能在运行时得到,所以需要把第二个方法作为参数传递给第一个方法。
2、可以实现几个想要的功能:

  • 将一个或多个方法作为参数传递给另一个方法,从而在需要时调用这些方法。
  • 实现事件处理程序。
  • 实现回调方法。
  • 实现异步编程等功能。

3、关于为什么委托是类型安全的。
在C和C++中,只能提取函数的地址,并作为一个参数传递它。C没有类型安全性,可以把任何函数传递给需要函数指针的方法。但是,这种直接方法不仅会导致一些关于类型安全性的问题,而且没有意识到:在进行面向对象编程时,几乎没有方法是孤立存在的,而是在调用方法前通常需要与类实例相关联。所以.NET Framework在语法上不允许使用这种直接方法。如果要传递方法,就必须把方法的细节封装在一种新类型的对象中,即委托。委托只是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。

三、委托的类型
  • Delegate 普通委托(类似函数指针)
    delegate void IntMethodInvoker(int x);
  • Action<T> 泛型委托(C#内置委托),void返回类型,有16种不同的参数类型。
  • Func<in T,out TResult> (C#内置委托),带返回类型的方法,TResult是返回值。至多也可以传递16个参数类型和一个返回类型。
三、关于委托能进行哪些操作

1、声明一个委托类型。委托声明看上去和C++中方法声明相似,只是没有实现块。
public delegate int MyDelegate(int num);
2、使用该委托类型声明一个委托变量。
3、创建一个委托类型的对象,并把它赋值给委托变量。新的委托对象包含指向某个方法的引用,这个方法的签名和返回类型必须跟第一步中定义的委托类型一致。
MyDelegate myDelegate = new MyDelegate(TestMyDelegate);(同时包含了2、3步)
4、你可以选择为委托对象添加其他方法。这些方法的签名和返回类型必须与第一步中定义的委托类型相同。
myDelegate += TestMyDelegate_B;
5、在代码中你可以像调用方法一样调用委托。在调用委托的时候,其包含的每一个方法都会被执行。
myDelegate(12);
此时TestMyDelegate和TestMyDelegate_B都会调用。
从使用过程可以看出,委托是引用类型,因此有引用和对象。在委托类型声明之后,我们可以声明变量并创建类型的对象。
6、多播委托还能识别运算符“-”和“-=”,以从委托中删除方法的调用。

三、与Lambda关系

从C#3.0开始,就可以使用一种新语法把实现代码赋予委托:Lambda表达式。只要有委托参数类型的地方,就可以使用Lambda表达式。
Lambda传参的方式:

  • 一个参数 eg:
Func<string, string> oneParam = s => s.Replace('a', 'b');
Console.WriteLine(oneParam("abc"));

结果:bbc

  • 如果委托使用多个参数,就把参数名放在小括号中
Func<int, int, int> twoParam = (i, j) => i * j;
Console.WriteLine(twoParam(2, 4));

结果:8

  • Lambda多行代码
Func<int, int, int> test = (i, j) =>
            {
                     i = i + 1;
                     i = i * j;
                     return i;
            };
Console.WriteLine(test(2, 4));

结果:12

又比如下面的两种写法是等同的:

Func<string, string> oneParam = s => s.Replace('a', 'b');
Func<string, string> oneParam2 = delegate (string s)
{
    return s.Replace('a', 'b');
};

第一个string实际是输入参数,第二个参数是返回值,Func是必须包含返回值的。

四、特殊委托(event 修饰)
public delegate int MyDelegate(int num);
public event MyDelegate myDelegate;//这里特意使用了

event来修饰。
光板 delegate对象,可以到处调用它。
有了event只允许+=,而不支持=,并且在其他类中不能直接调用委托了。只能在 “event 修饰的delegate对象” 所属类中使用。
如下,在在 “event 修饰的delegate对象” 所属类中可以使用myDelegate(12);


1701549047584.png

而在其他类中则不能这么调用,如下图:


1701549170976.png

C#的事件机制就是从这个特殊委托延伸而来的。
五、委托延伸-事件

GUI编程是事件驱动的,也就是说在程序运行时,它可以在任何时候被时间打断,比如按钮点击、按下按键或系统定时器。在这些情况发生时,程序需要处理事件然后继续做其他事件。

显然,程序事件的异步处理是使用C#事件的绝佳场景。Windows GUI编程如此广泛地使用了事件,以至于对于事件的使用,.NET框架提供了一个标准模式。该标准模式的基础就是System命名空间中声明的EventHandler委托类型。EventHandler委托类型的声明如下:

public delegate void Eventhandler(object sender, EventArgs e);
六、标准事件:EventHandler
class Incrementer
{
    public event EventHandler CountedADozen;
    //event:关键字
    //EventHandler:委托类型
    //CountedADozen:事件名
}
定义

从上面代码可以看出:
1、事件是声明在一个类中的,和方法、属性一样,事件是类或结构的成员。
2、事件成员被隐式自动初始化为null。
3、事件是C#语言中一种特殊的委托,是用于实现对象之间的通信的机制。

要求
  • 事件声明在一个类中。
  • 它需要委托类型的名称,任何附加到事件(如注册)的处理程序都必须与委托类型的签名和返回类型匹配
  • 它声明为public,这样其他类和结构可以在它上面注册事件处理程序。
  • 不能使用对象创建表达式(new 表达式)来创建它的对象。
示例代码
    class Publisher
    {
        public event EventHandler SimpleEvent;

        public void RaiseTheEvent() { SimpleEvent(this, null); }
    }

    class Subscriber
    {
        public void MethodA(object o, EventArgs e) { Console.WriteLine("AAA"); }
        public void MethodB(object o, EventArgs e) { Console.WriteLine("BBB"); }
    }

    class MyProgram
    {
        static void Main()
        {
            Publisher p = new Publisher();
            Subscriber s = new Subscriber();

            p.SimpleEvent += s.MethodA;
            p.SimpleEvent += s.MethodB;
            p.RaiseTheEvent();

            Console.WriteLine("\r\nRemove MethodB");
            p.SimpleEvent -= s.MethodB;
            p.RaiseTheEvent();
        }
    }

目的:Publisher持有事件引用,并触发事件,Subscriber只能通过订阅SomeEvent来获得事件通知
其中,事件的参数是可以传递过来给Subscriber的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容