当前位置: 首页 > article >正文

【从零开始入门unity游戏开发之——C#篇33】C#委托(`Delegate`)和事件(`event` )、事件与委托的区别、Invoke()的解释

文章目录

  • 一、委托(`Delegate`)
    • 1、什么是委托?
    • 2、委托的基本语法
    • 3、定义自定义委托
    • 4、如何使用自定义委托
    • 5、多播委托
    • 6、C# 中的系统委托
    • 7、`GetInvocationList` 获取多个函数返回值
    • 8、总结
  • 二、事件(`event` )
    • 1、事件是什么?
      • 1.1. **事件是基于委托的存在**:
      • 1.2. **事件是委托的安全包裹**:
      • 1.3. **事件是一种特殊的变量类型**:
    • 2、事件的使用
      • 2.1 **声明事件语法**:
      • 2.2 **事件的使用**:
      • 2.3 **事件的触发**:
    • 3、**委托与事件的区别**:
    • 4、为什么要使用事件?
      • 4.1. **防止外部随意置空委托**:
      • 4.2. **防止外部随意调用委托**:
      • 4.3. **事件封装了委托,增强了安全性**:
    • 5、事件与委托的区别总结
      • 5.1. **赋值操作**:
      • 5.2. **调用方式**:
      • 5.3. **临时变量**:
      • 5.4. **可见性和封装**:
    • 6、总结
  • 三、补充`Invoke()`的解释说明
  • 专栏推荐
  • 完结

一、委托(Delegate

1、什么是委托?

委托(Delegate)是 C# 中的一个重要概念,它可以被理解为“函数(方法)的容器”。更具体地说,委托是用来存储、传递函数(方法)的类型,允许我们将方法作为参数传递给其他方法或存储在变量中。委托的本质是一个类,用来定义方法的类型(即方法的返回值和参数类型)。

  • 委托本质上是一个类型,它定义了方法的类型:返回值类型和参数类型。
  • 委托用于存储方法,它帮助你把方法传递给其他方法,或者在程序中动态调用。
  • 委托是一个引用类型

2、委托的基本语法

  1. 关键字: delegate

  2. 语法:

     [访问修饰符] delegate 返回值类型 委托名(参数列表);
    

    示例:

    delegate void MyDelegate(); // 无返回值,无参数的委托
    delegate int MyDelegateWithParam(int x); // 返回 int,带一个参数的委托
    
  • 委托可以声明在类(class)或命名空间(namespace)中,一般情况下会声明在命名空间中。
  • 委托声明的访问修饰符可以是 public(默认是 public),也可以是 privateprotectedinternal 等。

3、定义自定义委托

  1. 无参无返回值的委托

    public delegate void MyFun();
    
  2. 带参数和返回值的委托

    public delegate int MyFun2(int a);
    
  3. 泛型委托:
    委托是支持泛型的,可以让返回值和参数的类型灵活变更。

    delegate T MyFun3<T, K>(T v, K k);
    

4、如何使用自定义委托

委托变量相当于一个函数的容器,可以存储函数,并通过委托来调用它。

  1. 基本用法

    • 通过 new 关键字创建委托实例,并调用它。
    public delegate void MyFun();
    
    static void Fun()
    {
        Console.WriteLine("Hello from Fun!");
    }
    
    static void Main(string[] args)
    {
        MyFun f = new MyFun(Fun); // 创建委托实例
        f.Invoke();  // 调用委托,输出: Hello from Fun!
    }
    
  2. 简化方式(委托隐式转换)
    委托可以直接引用方法,而不需要 new 操作符。

    MyFun f2 = Fun;
    f2(); // 调用 Fun() 方法
    
  3. 带参数的委托
    如果委托方法需要参数,可以在调用时传递参数。

    public delegate int MyFun2(int a);
    
    static int Fun2(int x)
    {
        return x * 2;
    }
    
    static void Main(string[] args)
    {
        MyFun2 f3 = Fun2;
        Console.WriteLine(f3(3)); // 输出: 6
    }
    
  4. 委托作为参数
    委托经常作为方法的参数,用于传递行为。

     public delegate void MyFun(); // 定义委托类型
     
     class Test()
     {
         public void TestFun(MyFun fun)
         {
             // 执行其他逻辑
             fun();  // 调用传入的委托方法
         }
     }
     
     class Program
     {
         static void FunLog()
         {
             Console.WriteLine("Hello from Fun!");
         }
     
         public static void Main()
         {
             MyFun Fun = FunLog; // 创建委托实例
     
             Test t = new Test();
             t.TestFun(Fun); // 传递委托
         }
     }
    

    结果打印
    在这里插入图片描述

5、多播委托

委托可以存储多个方法(多播委托)。通过多播委托,可以在一个委托实例中注册多个方法,调用时按顺序执行这些方法。

  1. 添加多个方法到委托

    MyFun f = Fun;
    f += Fun2;
    f();  // 调用 Fun 和 Fun2 方法
    
  2. 移除方法

    f -= Fun;
    f();  // 只调用 Fun2 方法
    
  3. 清空所有注册的方法

    f = null;
    

6、C# 中的系统委托

C# 提供了多个预定义的委托类型,主要有 ActionFunc。这些委托可以大大简化我们的编程工作。

  1. Action 委托
    Action 委托用于没有返回值的情况。它可以有0到16个参数。

    Action委托的语法结构

    Action<参数类型1, 参数类型2, ..., 参数类型N>
    
    • 无参数:

      Action action = Fun;
      action += Fun2;
      action();// 调用 Fun 和 Fun2 方法
      
    • 带参数:

      Action<int, string> action2 = Fun2;
      action2(1, "Hello");
      
  2. Func 委托
    Func 委托用于有返回值的情况。它也支持0到16个参数。
    Func 委托的最后一个类型参数总是方法的返回值类型,而其他的参数类型则是方法的输入参数类型。

    Func 委托的语法结构

    Func<参数类型1, 参数类型2, ..., 参数类型N, 返回值类型>
    
    • 无参数,有返回值:

      static int FunLog()
      {
         return 1;
      }
      
      public static void Main()
      {
         Func<int> func = FunLog;
         int result = func();
         Console.WriteLine(result);// 打印1
      }
      
    • 带参数,有返回值:

         static int FunLog(string str)
         {
             return int.Parse(str);
         }
      
         public static void Main()
         {
             Func<string, int> func = FunLog;
             int result = func("111");
             Console.WriteLine(result);// 打印111
         }
      

7、GetInvocationList 获取多个函数返回值

当用有返回值的委托容器存储多个函数时

using System;

class Program
{
    static void Main()
    {
        // 创建一个 Func 委托,返回 string 类型
        Func<string> funTest = () =>
        {
            Console.WriteLine("第一个函数");
            return "1";
        };

        // 将更多函数添加到委托链
        funTest += () =>
        {
            Console.WriteLine("第二个函数");
            return "2";
        };

        funTest += () =>
        {
            Console.WriteLine("第三个函数");
            return "3";
        };

        // 如果直接调用委托,会执行所有函数逻辑,但是只能获取到最后一个返回值
        Console.WriteLine("直接调用返回值:");
        Console.WriteLine(funTest());  // 只会输出 "3"
    }
}

输出结果:

直接调用返回值:
第一个函数
第二个函数
第三个函数
3

如果想要获取每个函数的返回值,可以通过 GetInvocationList 方法

foreach (Func<string> del in funTest.GetInvocationList())
{
    Console.WriteLine(del());  // 输出每个函数的返回值
}

输出结果:

第一个函数
1
第二个函数
2
第三个函数
3
  • funTest.GetInvocationList() 返回一个委托数组,数组中的每个元素都是一个 Func 委托实例,表示容器中每个方法。
  • 我们通过 foreach 遍历这些委托实例,并单独执行每个方法,获取它们的返回值。

8、总结

  • 委托 是 C# 中用来装载方法的类型,允许将方法作为参数传递或者存储。
  • 委托的定义语法包括 delegate 关键字,后接返回类型、委托名称以及参数列表。
  • 委托支持泛型,可以创建灵活的、可重用的函数容器。
  • 委托可以作为方法的参数,或作为类的成员变量,用于实现事件、回调等功能。
  • C# 提供了 没有返回值Action 和 有返回值Func 等预定义的委托类型,可以减少开发者手动定义委托的工作量。

二、事件(event

在 C# 中,事件是基于委托的,它对委托进行了封装,提供了一些安全性和约束。事件是 C# 中一种特殊的成员,通常用于发布-订阅模式,允许一个对象(发布者)通知其他对象(订阅者)某些事情的发生。

1、事件是什么?

1.1. 事件是基于委托的存在

  • 委托可以理解为对方法的引用,而事件是基于委托的,委托在事件中扮演着核心角色。

1.2. 事件是委托的安全包裹

  • 事件通过封装委托,限制了委托的使用,使其更具安全性。特别是防止外部代码随意更改委托(赋值和调用)。

1.3. 事件是一种特殊的变量类型

  • 在 C# 中,事件是一种特殊的变量,它用于存储委托类型的引用,但它具有一些额外的限制。

2、事件的使用

2.1 声明事件语法

事件的声明语法如下:

[访问修饰符] event 委托类型 事件名;

例如:

public event Action MyEvent;

2.2 事件的使用

  • 事件作为类的成员变量,类似于委托。
  • 事件和委托的使用方法非常相似,主要的区别在于事件增加了访问限制。

示例:

public class Test
{
    public Action MyFun;  // 委托类型成员变量
    public event Action MyEvent;  // 事件类型成员变量
}

2.3 事件的触发

  • 事件的触发只能在事件的拥有者(类)内部进行,通过调用事件处理方法。
  • 一般来说,可以通过一个方法来触发事件,例如:
public void DoEvent()
{
    if (MyEvent != null)
    {
        MyEvent.Invoke();  // 安全地触发事件
    }
}

3、委托与事件的区别

  • 事件不能在类外部赋值:委托可以在类外部进行赋值(例如:myFun = TestFun),但事件只能通过 +=-= 操作符来添加和移除事件处理器。
  • 事件不能在类外部调用:委托可以在类外部直接调用(例如:myFun()),而事件只能在类内部触发(通常使用事件发布方法)。

示例:

public void TestFun()
{
    Console.WriteLine("123");
}

// 委托使用
myFun += TestFun;
myFun();  // 调用委托

// 事件使用
myEvent += TestFun;  // 添加事件处理器
myEvent();  // 错误,无法在类外部调用事件

4、为什么要使用事件?

4.1. 防止外部随意置空委托

  • 事件的最大优势之一是它防止了外部代码直接修改委托的值(置空)。这保证了事件的安全性,防止委托被外部代码随意设置为 null 或重新赋值,避免了潜在的错误。

4.2. 防止外部随意调用委托

  • 事件提供了对委托的封装,确保事件只能通过 +=-= 操作符来添加和移除事件处理器,不能通过外部代码直接调用。

4.3. 事件封装了委托,增强了安全性

  • 事件是一种委托的封装,提供了更高的安全性和可控性,使得事件处理过程更加规范。外部代码无法直接修改事件的内容,只能通过规范的方式来订阅和退订事件。

5、事件与委托的区别总结

5.1. 赋值操作

  • 委托:可以在外部赋值(=)。
  • 事件:不能直接赋值,只能通过 +=-= 操作符来添加和移除事件处理器。

5.2. 调用方式

  • 委托:可以在外部直接调用(myFun())。
  • 事件:只能在类内部通过触发方法调用(MyEvent.Invoke())。

5.3. 临时变量

  • 委托:可以作为局部变量在方法中使用。
  • 事件:不能作为临时变量,只能作为类的成员存在。

5.4. 可见性和封装

  • 委托:没有访问控制的封装机制,外部可以自由操作。
  • 事件:有封装,提供了更严格的访问控制,确保安全性。

6、总结

  • 事件本质上是一个委托的封装,提供了额外的安全性。它限制了委托在外部的赋值和调用,使得事件的触发只能在拥有事件的类内部进行。
  • 事件的主要用途是实现发布-订阅模式,当某个对象发生变化时,能够通知其他对象,避免外部代码直接修改和调用事件。
  • 事件与委托的主要区别在于访问权限的控制,事件通过严格的封装和访问控制来增强安全性,避免外部随意操作。

三、补充Invoke()的解释说明

Invoke() 是委托事件中常用的方法,它的作用是调用委托所指向的方法。当你在委托或者事件上调用 Invoke() 时,它会执行该委托或事件注册的所有方法。

示例:

public delegate void MyDelegate(string message);

public class Program
{
    static void Main()
    {
        MyDelegate myDelegate = new MyDelegate(PrintMessage);
        myDelegate.Invoke("Hello, World!");  // 调用委托的 Invoke 方法
    }

    static void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}

Invoke() 是隐式调用的,因此你也可以直接使用 myDelegate() 来调用它。这两者的作用是相同的。实际上,Invoke() 是自动由 C# 编译器生成的,如果你使用 myDelegate(),编译器会在幕后调用 Invoke()

对于事件同理,Invoke() 方法用于触发事件并调用所有注册的事件处理程序。事件本质上是封装了委托,因此通过 Invoke() 可以调用所有订阅该事件的方法。


专栏推荐

地址
【从零开始入门unity游戏开发之——C#篇】
【从零开始入门unity游戏开发之——unity篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架开发】

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述


http://www.kler.cn/a/454534.html

相关文章:

  • 谷歌SEO-关键词研究
  • 【ES6复习笔记】迭代器(10)
  • Vue3 Suspense:处理异步渲染过程
  • 理解神经网络
  • [Visual studio] 性能探测器
  • Linux运维常见命令
  • Spring Boot的开发工具(DevTools)模块中的热更新特性导致的问题
  • Vue3 Suspense:处理异步渲染过程
  • 力扣-数据结构-4【算法学习day.75】
  • EleutherAI/pythia-70m
  • 联通移动大内网如何使用plex流媒体服务器
  • 讲一个自己写的 excel 转 html 的 java 工具
  • 三只脚的电感是什么东西?
  • Unity2021.3.16f1可以正常打开,但是Unity2017.3.0f3却常常打开闪退或者Unity2017编辑器运行起来就闪退掉
  • 更改 pnpm 的全局存储位置
  • User Script Sandboxing作用 及 在iOS项目中获取GitCommitHash
  • MacOS安装Xcode(非App Store)
  • 2-197 基于matlab的生物地理学优化算法(BBO)在无人机三维航迹规划中的应用
  • Nature+Science=ONNs(光学神经网络)
  • html文件通过script标签引入外部js文件,但没正确加载的原因
  • 1_H5视频播放器-1 -- [前端开发之道:通过实例掌握编程思维]
  • Centos7配置webrtc-streamer环境
  • 识别后端返回的字符串中携带的空格 以及换行 要在前端展示 v-html
  • Python初识
  • MySQL从入门到入土---MySQL表的约束 (内含实践)---详细版
  • 火山引擎边缘云全面升级智能边缘,推动 AI 应用场景拓展与技术创新