C#委托、事件与回调详解:概念区分及代码实战

江屿
江屿
发布时间:2026-10-15| 4人浏览过
原创

在C#开发中,委托(Delegate)、事件(Event)和回调(Callback)是处理方法与动态调用的核心机制。委托定义了方法的签名,使方法可以作为参数传递;事件基于委托,增加了安全性限制,防止外部非法修改调用列表;回调则是一种设计模式,将具体逻辑的处理权交给调用方。本文将深入解析这三者的原理与区别,并通过实例展示如何在实际编码中运用它们来提升代码的扩展性与解耦程度。

核心概念:委托、事件与回调的区别

理解这三者的关键在于它们的职责边界:

  • 委托 (Delegate):这是一个类,定义了方法的类型。它允许将方法当作另一个方法的参数进行传递,从而避免大量的 if-else 或 switch 语句,提升程序的可扩展性。
  • 事件 (Event):是对委托的封装。如果直接暴露委托,调用者可以重新引用委托对象,从而覆盖原有的方法列表。使用 event 关键字后,外部只能使用 += 和 -= 订阅或取消订阅,无法直接赋值或清空调用列表,保障了内部状态的安全。
  • 回调函数 (Callback):并非C#特有的关键字,而是一种编程思想。指将一个函数(方法)作为参数传递给另一个函数,后者在特定时刻执行该参数。回调本质上是利用委托实现的,常用于系统底层与上层业务逻辑的解耦。

基础案例:委托的定义与使用

委托的使用分为两步:定义委托类型和关联方法。以下示例展示了如何定义一个接收两个整数并返回整数的委托,并将其绑定到具体的计算方法上。

第1步:定义委托与方法

首先需要声明一个与目标方法签名一致的委托。在下面的代码中,我们定义了一个名为 DelegateTest 的委托,以及对应的数学运算类。

    // 1. 定义委托 (签名必须与要绑定的方法一致)
    public delegate int DelegateTest(int n1, int n2);

    class Math
    {
        // 2. 定义具体方法
        public int Multiply(int n1, int n2)
        {
            return n1 * n2;
        }

        public int AddTest(int n1, int n2)
        {
            return n1 + n2;
        }
    }

第2步:实例化与调用

在实际应用中,可以通过将方法赋值给委托变量来建立关联。C# 提供了 Func 泛型委托来简化这一过程,当方法有返回值时优先使用 Func。

    private void button1_Click(object sender, EventArgs e)
    {
        Math objMath = new Math();

        // 方式一:自定义委托
        DelegateTest delegateDemo1 = objMath.Multiply;
        // 可选:多播委托,添加另一个方法
        // delegateDemo1 += objMath.AddTest;

        int result = delegateDemo1(5, 12); // 结果为 60
        richTextBox1.AppendText(result.ToString() + "\r\n");

        // 方式二:使用 Func<T> 简化 (推荐)
        // Func<参数类型..., 返回类型>
        Func<int, int, int> func1 = objMath.Multiply;
        richTextBox1.AppendText(func1.Invoke(6, 13) + "\r\n");
    }

进阶案例:事件的安全封装

事件的核心价值在于“封装”。通过 event 关键字,我们可以确保只有事件拥有者能触发通知,而订阅者只能选择加入或离开。

第1步:定义事件发布者

在 DelegatEventTest 类中,首先定义委托,然后使用 event 关键字声明事件。在触发前检查是否为 null 是好习惯。

    public delegate void MyDelegateEventHandler();

    class DelegatEventTest
    {
        // 声明事件
        public event MyDelegateEventHandler NotifyEveryOne;

        // 触发事件的方法
        public void Notify()
        {
            if (NotifyEveryOne != null)
            {
                NotifyEveryOne();
            }
        }
    }

第2步:订阅事件

在使用者端,创建发布者和消费者对象,并通过 += 订阅事件。此时,外部代码无法执行 = 赋值操作,保证了安全。

    class ClassA { public string ClassAinfo = "A 默认!"; public void DispInfo(){ ClassAinfo = "A 收到!"; } }
    class ClassB { public string ClassAinfo = "B 默认!"; public void DispInfo(){ ClassAinfo = "B 收到!"; } }

    private void button3_Click(object sender, EventArgs e)
    {
        ClassA objA = new ClassA();
        ClassB objB = new ClassB();
        DelegatEventTest eventPublisher = new DelegatEventTest();

        // 订阅:ClassA 的对象方法挂接到事件
        eventPublisher.NotifyEveryOne += new DelegatEventTest.MyDelegateEventHandler(objA.DispInfo);
        // 也可以继续订阅其他方法: eventPublisher.NotifyEveryOne += objB.DispInfo;

        // 触发事件
        eventPublisher.Notify();

        richTextBox1.AppendText(objA.ClassAinfo + "\r\n");
        richTextBox1.AppendText(objB.ClassAinfo + "\r\n");
    }

实战演练:带参数的复杂事件交互

在实际业务中,事件往往需要传递上下文信息。以下案例模拟了“鸿门宴”场景:首领 BoyK 举杯或摔杯,部下列队响应。不同动作触发不同的参数,部下列根据参数决定行动。

第1步:定义首领 (事件源)

BoyK 定义了两个事件:一个是带字符串参数的举杯事件,一个是无参数的摔杯事件。

    public class BoyK
    {
        public delegate void RaiseEventHandler(string hand);
        public delegate void FallEventHandler();

        public event RaiseEventHandler RaiseEvent;
        public event FallEventHandler FallEvent;

        public void Raise(string hand)
        {
            RaiseEvent?.Invoke(hand); // C# 6+ 安全调用写法
        }

        public void Fall()
        {
            FallEvent?.Invoke();
        }
    }

第2步:定义部下 (事件订阅者)

BoyA 和 BoyB 在构造函数中订阅首领的事件。他们根据传入的手势参数(左/右)判断是否行动。

    class BoyA
    {
        public string str = "A待命";
        BoyK boyk;
        public BoyA(BoyK k)
        {
            boyk = k;
            // 订阅举杯和摔杯事件
            boyk.RaiseEvent += k_RaiseEvent;
            boyk.FallEvent += k_FallEvent;
        }

        public void Attack() { str = "A开始攻击"; }

        void k_RaiseEvent(string hand)
        {
            // 仅当左手举杯时行动
            if (hand.Equals("左")) Attack();
        }

        void k_FallEvent() { Attack(); } // 摔杯即行动
    }

    class BoyB
    {
        public string str = "B待命";
        BoyK boyk;
        public BoyB(BoyK k)
        {
            boyk = k;
            boyk.RaiseEvent += k_RaiseEvent;
            boyk.FallEvent += k_FallEvent;
        }

        public void Attack() { str = "B开始攻击"; }

        void k_RaiseEvent(string hand)
        {
            // 仅当右手举杯时行动
            if (hand.Equals("右")) Attack();
        }

        void k_FallEvent() { Attack(); }
    }

第3步:执行交互

在主程序中创建对象并触发动作。由于 BoyA 监听“左”,BoyB 监听“右”,调用 Raise("右") 只会激活 BoyB。

    private void button2_Click(object sender, EventArgs e)
    {
        BoyK boyK = new BoyK();
        BoyA boyA = new BoyA(boyK);
        BoyB boyB = new BoyB(boyK);

        // 触发右侧举杯
        boyK.Raise("右");

        richTextBox1.AppendText(boyA.str + "\r\n"); // A仍待命
        richTextBox1.AppendText(boyB.str + "\r\n"); // B已行动
    }

架构模式:回调函数的应用

回调常用于插件式开发或底层SDK封装。系统只提供执行框架,具体算法由开发者通过回调传入。

第1步:封装底层计算类

此类不关心具体计算逻辑,只负责接收一个回调委托并执行。

    class CalculateClass
    {
        public delegate int SomeCalculateWay(int num1, int num2);

        // 核心方法:num1, num2为数据,call为回调函数
        public int Calculate(int num1, int num2, SomeCalculateWay call)
        {
            return call(num1, num2);
        }
    }

第2步:实现具体业务逻辑

开发者编写具体的计算策略类。

    class FunctionClass
    {
        public int GetSum(int a, int b) => a + b;
        public int GetMulti(int a, int b) => a * b;
    }

第3步:用户层调用

用户无需直接调用 GetSum,而是将其传给 CalculateClass。这种解耦使得未来替换算法时无需修改底层框架代码。

    private void button4_Click(object sender, EventArgs e)
    {
        CalculateClass cc = new CalculateClass();
        FunctionClass fc = new FunctionClass();

        // 传入加法回调
        int result1 = cc.Calculate(2, 3, fc.GetSum);
        // 传入乘法回调
        int result2 = cc.Calculate(2, 3, fc.GetMulti);

        richTextBox1.AppendText("Sum: " + result1 + "\r\n");
        richTextBox1.AppendText("Multi: " + result2 + "\r\n");
    }

在上述结构中,GetSum 和 GetMulti 即为回调函数。它们没有被直接调用,而是作为参数传递给了系统方法,实现了控制权的反转。

总结

委托、事件和回调是C#实现松耦合和高扩展性的基石。委托是类型安全的函数指针;事件是在委托基础上增加访问控制的机制,适合观察者模式;回调则是利用委托将处理逻辑参数化的设计模式。掌握它们的正确使用场景,能显著提升代码的模块化程度。以上就是C#中委托、事件和回调的使用及说明的详细内容,更多关于C#高级编程技巧的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。