C# 中的委托(Delegate)与事件(Event)

一、委托(Delegate):类型安全的 "函数指针"

1. 核心概念

委托是 C# 中的一种引用类型 ,它的作用是 "封装" 一个或多个方法(方法的签名和返回值必须匹配),可以把它理解为一个类型安全的函数指针。你可以把方法当作参数传递、存储或调用,这是实现回调、解耦代码的基础。

2. 基本用法(完整代码示例)
复制代码
using System;

// 1. 定义委托:确定要封装的方法的签名(返回值+参数)
public delegate void GreetingDelegate(string name);

class Program
{
    // 2. 定义匹配委托签名的方法
    static void ChineseGreeting(string name)
    {
        Console.WriteLine($"你好,{name}!");
    }

    static void EnglishGreeting(string name)
    {
        Console.WriteLine($"Hello, {name}!");
    }

    static void Main(string[] args)
    {
        // 3. 创建委托实例并绑定方法
        GreetingDelegate greet1 = ChineseGreeting;
        // 调用委托(本质是调用绑定的方法)
        greet1("张三"); // 输出:你好,张三!

        // 4. 委托的多播(绑定多个方法)
        GreetingDelegate greetAll = ChineseGreeting;
        greetAll += EnglishGreeting; // += 添加方法
        greetAll("李四"); // 依次调用两个方法,输出:你好,李四! Hello, 李四!

        // 5. 移除委托中的方法
        greetAll -= ChineseGreeting;
        greetAll("王五"); // 仅输出:Hello, 王五!
    }
}
3. 关键解释
  • 委托定义public delegate void GreetingDelegate(string name); 规定了委托能封装的方法必须是:无返回值、参数为 1 个 string 类型。

  • 委托实例化:直接将方法名赋值给委托变量(无需括号),因为委托本质是封装方法。

  • 多播委托 :通过+=添加方法、-=移除方法,调用时会按添加顺序执行所有绑定的方法(如果有返回值,仅返回最后一个方法的结果)。

  • 常用内置委托 :C# 提供了Action<T>(无返回值)、Func<T,TResult>(有返回值)等内置委托,无需手动定义:

    复制代码
    // 使用内置Action委托替代自定义GreetingDelegate
    Action<string> greet = ChineseGreeting;
    greet("赵六"); // 输出:你好,赵六!

    二、事件(Event):委托的 "安全封装"

    1. 核心概念

    事件是基于委托的一种特殊机制 ,它限制了委托的访问权限:外部类只能通过+=订阅事件、-=取消订阅,无法直接调用委托或用=覆盖委托(避免意外修改),是实现 "发布 - 订阅" 模式的核心。

    2. 基本用法(完整代码示例:发布 - 订阅模式)
    复制代码
    using System;
    
    // 发布者类:触发事件的一方
    public class AlarmClock
    {
        // 1. 定义委托(事件的"签名")
        public delegate void RingEventHandler(object sender, EventArgs e);
    
        // 2. 定义事件:基于上面的委托
        public event RingEventHandler Ring;
    
        // 3. 触发事件的方法(通常是protected virtual,供子类重写)
        protected virtual void OnRing()
        {
            // 先判断是否有订阅者,避免空引用异常
            Ring?.Invoke(this, EventArgs.Empty);
        }
    
        // 模拟闹钟响铃
        public void Start(int seconds)
        {
            Console.WriteLine($"闹钟开始计时,{seconds}秒后响铃...");
            System.Threading.Thread.Sleep(seconds * 1000); // 模拟等待
            OnRing(); // 触发事件
        }
    }
    
    // 订阅者类1:响应事件的一方
    public class Person
    {
        private string _name;
    
        public Person(string name)
        {
            _name = name;
        }
    
        // 订阅事件的方法(签名必须匹配委托)
        public void WakeUp(object sender, EventArgs e)
        {
            Console.WriteLine($"{_name}:听到闹钟响,起床啦!");
        }
    }
    
    // 订阅者类2
    public class Cat
    {
        public void React(object sender, EventArgs e)
        {
            Console.WriteLine("小猫:被闹钟吵醒,喵喵叫!");
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            // 1. 创建发布者(闹钟)
            AlarmClock clock = new AlarmClock();
    
            // 2. 创建订阅者
            Person tom = new Person("Tom");
            Cat kitty = new Cat();
    
            // 3. 订阅事件:将订阅者的方法绑定到事件
            clock.Ring += tom.WakeUp;
            clock.Ring += kitty.React;
    
            // 4. 启动闹钟(触发事件)
            clock.Start(2);
    
            // 5. 取消订阅
            clock.Ring -= kitty.React;
            Console.WriteLine("\n取消小猫的订阅后,再次响铃:");
            clock.Start(1);
        }
    }
    3. 输出结果
    复制代码
    闹钟开始计时,2秒后响铃...
    Tom:听到闹钟响,起床啦!
    小猫:被闹钟吵醒,喵喵叫!
    
    取消小猫的订阅后,再次响铃:
    闹钟开始计时,1秒后响铃...
    Tom:听到闹钟响,起床啦!
    4. 关键解释
  • 事件定义public event RingEventHandler Ring; 事件必须基于委托定义,且通常放在类内部。

  • 触发事件 :通过OnXXX()方法触发(命名规范:On + 事件名),用Ring?.Invoke()避免空引用(没有订阅者时委托为 null)。

  • 访问限制 :外部类无法直接调用clock.Ring()clock.Ring = tom.WakeUp,只能用+=/-=,保证了代码安全性。

  • 标准事件参数 :实际开发中常用EventHandler<TEventArgs>内置委托,无需自定义委托:

    cs 复制代码
    // 替换自定义委托,使用内置EventHandler
    public event EventHandler Ring;
    // 触发逻辑不变:Ring?.Invoke(this, EventArgs.Empty);

    三、委托与事件的核心区别

    特性 委托(Delegate) 事件(Event)
    访问权限 外部可直接调用、覆盖 外部仅能订阅 / 取消订阅
    本质 引用类型(函数指针) 委托的封装(特殊语法)
    适用场景 灵活的方法封装、回调 发布 - 订阅模式、解耦组件
    多播支持 支持(+=/-=) 支持(底层依赖委托多播)

    总结

  • 委托是封装方法的引用类型,可多播,是事件的基础,核心作用是将方法作为参数传递 / 调用。

  • 事件是委托的安全封装,限制了外部对委托的操作,仅开放订阅 / 取消订阅,是实现发布 - 订阅模式的标准方式。

  • 实际开发中优先使用Action<T>/Func<T,TResult>(内置委托)和EventHandler<T>(事件委托),减少自定义委托的代码量。

相关推荐
用户21991679703911 小时前
C# 14 中的新增功能
c#
垂葛酒肝汤2 小时前
放置挂机游戏的离线和在线收益unity实现
游戏·unity·c#
爱说实话4 小时前
C# 20260112
开发语言·c#
无风听海4 小时前
C#中实现类的值相等时需要保留null==null为true的语义
开发语言·c#
云草桑4 小时前
海外运单核心泡货计费术语:不计泡、计全泡、比例分泡
c#·asp.net·net·计泡·海运
精神小伙就是猛5 小时前
C# Task/ThreadPool async/await对比Golang GMP
开发语言·golang·c#
工程师0075 小时前
C#状态机
开发语言·c#·状态模式·状态机
开开心心_Every5 小时前
离线黑白照片上色工具:操作简单效果逼真
java·服务器·前端·学习·edge·c#·powerpoint