404 Not Found

404 Not Found


nginx

委托与事件

委托概念

委托(Delegate)是C#中一种类型安全的函数指针,它可以将方法作为参数传递。委托定义了方法的签名(返回类型和参数列表),只有匹配该签名的方法才能赋值给委托实例。

与C/C++函数指针不同,委托是面向对象的、类型安全的,并且支持多播调用。

声明与使用委托

使用 delegate 关键字声明委托类型,然后创建实例并关联方法。

示例

CSHARP
delegate int MathOp(int a, int b);

class Program
{
    static int Add(int a, int b) => a + b;
    static int Multiply(int a, int b) => a * b;

    static void Main()
    {
        MathOp op = Add;
        Console.WriteLine(op(3, 4));

        op = Multiply;
        Console.WriteLine(op(3, 4));
    }
}
▶ 试一试
TEXT
7
12

多播委托

委托支持多播(Multicast),即一个委托实例可以引用多个方法。使用 += 添加方法,-= 移除方法。调用时按添加顺序依次执行所有方法。对于有返回值的委托,多播调用仅返回最后一个方法的结果。

示例

CSHARP
delegate void NotifyHandler(string msg);

class Program
{
    static void LogToConsole(string msg) => Console.WriteLine($"控制台: {msg}");
    static void LogToFile(string msg) => Console.WriteLine($"文件: {msg}");
    static void LogToDb(string msg) => Console.WriteLine($"数据库: {msg}");

    static void Main()
    {
        NotifyHandler handler = LogToConsole;
        handler += LogToFile;
        handler += LogToDb;

        handler("系统启动");

        Console.WriteLine("---");

        handler -= LogToFile;
        handler("系统关闭");
    }
}
▶ 试一试
TEXT
控制台: 系统启动
文件: 系统启动
数据库: 系统启动
---
控制台: 系统关闭
数据库: 系统关闭

内置委托

C#提供了三种常用内置委托,无需自定义即可覆盖大多数场景。

Action

Action 表示无返回值的方法。支持0到16个泛型参数:ActionAction<T>Action<T1,T2> 等。

示例

CSHARP
class Program
{
    static void Main()
    {
        Action<string> greet = name => Console.WriteLine($"你好, {name}!");
        greet("张三");

        Action noParam = () => Console.WriteLine("无参数Action");
        noParam();
    }
}
▶ 试一试
TEXT
你好, 张三!
无参数Action

Func

Func<T, TResult> 表示有返回值的方法。最后一个泛型参数为返回类型,前面为输入参数:Func<TResult>Func<T, TResult>Func<T1,T2,TResult> 等。

示例

CSHARP
class Program
{
    static void Main()
    {
        Func<int, int, int> add = (a, b) => a + b;
        Console.WriteLine(add(10, 20));

        Func<string> getTime = () => DateTime.Now.ToString("HH:mm:ss");
        Console.WriteLine(getTime());
    }
}
▶ 试一试
TEXT
30
14:30:00

Predicate

Predicate<T> 表示返回 bool 的方法,常用于筛选和判断。

示例

CSHARP
class Program
{
    static void Main()
    {
        Predicate<int> isEven = n => n % 2 == 0;
        Console.WriteLine(isEven(4));
        Console.WriteLine(isEven(7));

        var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
        var evens = numbers.FindAll(isEven);
        Console.WriteLine(string.Join(", ", evens));
    }
}
▶ 试一试
TEXT
True
False
2, 4, 6

匿名方法

匿名方法使用 delegate 关键字内联定义代码块,无需单独命名方法。

示例

CSHARP
class Program
{
    static void Main()
    {
        Func<int, int> square = delegate(int x) { return x * x; };
        Console.WriteLine(square(5));

        Action<string> log = delegate(string msg) { Console.WriteLine($"[LOG] {msg}"); };
        log("匿名方法示例");
    }
}
▶ 试一试
TEXT
25
[LOG] 匿名方法示例
💡 匿名方法是Lambda表达式的前身,现代C#开发中更推荐使用Lambda表达式 x => x * x,语法更简洁。

事件

事件(Event)是基于委托的发布-订阅机制。使用 event 关键字修饰委托字段后,只有声明事件的类内部可以触发事件,外部类只能通过 +=-= 订阅或取消订阅,无法直接调用。

示例

CSHARP
class Button
{
    public event EventHandler<string> OnClick;

    public void Click()
    {
        OnClick?.Invoke(this, "按钮被点击");
    }
}

class Program
{
    static void Main()
    {
        var btn = new Button();
        btn.OnClick += (sender, msg) => Console.WriteLine($"收到事件: {msg}");
        btn.Click();
        btn.Click();
    }
}
▶ 试一试
TEXT
收到事件: 按钮被点击
收到事件: 按钮被点击
⚠️ 如果没有订阅者,直接调用 OnClick.Invoke() 会抛出 NullReferenceException,因此应使用 ?.Invoke() 空条件调用。

发布-订阅模式

发布-订阅模式(Publisher-Subscriber)是事件的核心应用场景。发布者负责触发事件,订阅者负责响应事件,两者完全解耦。

示例

CSHARP
class NewsPublisher
{
    public event EventHandler<string> OnNewsPublished;

    public void Publish(string news)
    {
        Console.WriteLine($"发布新闻: {news}");
        OnNewsPublished?.Invoke(this, news);
    }
}

class NewsSubscriber
{
    public string Name { get; }

    public NewsSubscriber(string name)
    {
        Name = name;
    }

    public void Subscribe(NewsPublisher publisher)
    {
        publisher.OnNewsPublished += HandleNews;
    }

    public void Unsubscribe(NewsPublisher publisher)
    {
        publisher.OnNewsPublished -= HandleNews;
    }

    private void HandleNews(object sender, string news)
    {
        Console.WriteLine($"  [{Name}] 收到: {news}");
    }
}

class Program
{
    static void Main()
    {
        var publisher = new NewsPublisher();
        var sub1 = new NewsSubscriber("读者A");
        var sub2 = new NewsSubscriber("读者B");

        sub1.Subscribe(publisher);
        sub2.Subscribe(publisher);

        publisher.Publish("C# 12 正式发布");

        sub2.Unsubscribe(publisher);
        publisher.Publish(".NET 9 性能提升50%");
    }
}
▶ 试一试
TEXT
发布新闻: C# 12 正式发布
  [读者A] 收到: C# 12 正式发布
  [读者B] 收到: C# 12 正式发布
发布新闻: .NET 9 性能提升50%
  [读者A] 收到: .NET 9 性能提升50%

自定义 EventArgs

EventHandler<T> 中的 T 通常继承自 EventArgs,用于携带自定义事件数据。

示例

CSHARP
class TemperatureEventArgs : EventArgs
{
    public double Temperature { get; }
    public DateTime Timestamp { get; }

    public TemperatureEventArgs(double temperature)
    {
        Temperature = temperature;
        Timestamp = DateTime.Now;
    }
}

class TemperatureMonitor
{
    public event EventHandler<TemperatureEventArgs> OnTemperatureAlert;
    private double _threshold;

    public TemperatureMonitor(double threshold)
    {
        _threshold = threshold;
    }

    public void CheckTemperature(double current)
    {
        Console.WriteLine($"当前温度: {current}°C");
        if (current > _threshold)
        {
            var args = new TemperatureEventArgs(current);
            OnTemperatureAlert?.Invoke(this, args);
        }
    }
}

class Cooler
{
    public void StartCooling(object sender, TemperatureEventArgs e)
    {
        Console.WriteLine($"  🚨 温度超限! {e.Temperature}°C > 阈值, 启动降温 (时间: {e.Timestamp:HH:mm:ss})");
    }
}

class Program
{
    static void Main()
    {
        var monitor = new TemperatureMonitor(30.0);
        var cooler = new Cooler();

        monitor.OnTemperatureAlert += cooler.StartCooling;

        monitor.CheckTemperature(25.0);
        monitor.CheckTemperature(32.5);
        monitor.CheckTemperature(28.0);
        monitor.CheckTemperature(35.0);
    }
}
▶ 试一试
TEXT
当前温度: 25°C
当前温度: 32.5°C
  🚨 温度超限! 32.5°C > 阈值, 启动降温 (时间: 14:30:00)
当前温度: 28°C
当前温度: 35°C
  🚨 温度超限! 35°C > 阈值, 启动降温 (时间: 14:30:01)

委托与事件的区别

特性 委托 事件
本质 类型(函数指针) 修饰符(受限委托实例)
外部调用 任何地方可调用 仅声明类内部可触发
外部赋值 可用 = 覆盖 只能用 += / -=
用途 回调、策略模式 发布-订阅、通知机制
安全性 较低 较高,封装触发权限
📌 事件本质上是委托加上 event 修饰符后的封装,限制了外部对委托的调用和赋值权限,从而保证只有发布者能触发事件。

❓ 常见问题

Q 委托和事件有什么区别?
A 事件是加上了 event 修饰符的委托,外部只能用 +=/-= 订阅,无法直接调用或覆盖,只有声明类内部可以触发。
Q 多播委托有返回值时返回什么?
A 返回最后一个被调用方法的返回值,前面的返回值会被丢弃。
Q Action 和 Func 有什么区别?
A Action 无返回值(void),Func 有返回值,最后一个泛型参数为返回类型。
Q 为什么要用 ?.Invoke() 调用事件?
A 防止事件无订阅者时抛出 NullReferenceException?.Invoke() 在事件为 null 时自动跳过。
Q Predicate 和 Func<T, bool> 等价吗?
A 功能等价,Predicate<T>Func<T, bool> 的特化版本,Predicate<T> 语义更明确,常用于筛选场景。

📖 小节

📝 作业

  1. 定义一个 delegate string Transform(string s); 委托,分别关联转大写、转小写、反转字符串三个方法,并测试多播调用
  2. 使用 Func<int, int, bool> 实现一个比较器,判断第一个数是否大于第二个数
  3. 创建一个 FileWatcher 类,定义 event EventHandler<string> OnFileChanged,模拟文件变更时通知订阅者
  4. 自定义 StockPriceEventArgs(包含股票代码、价格、涨跌幅),实现一个股票价格监控器,当涨跌幅超过阈值时触发事件
  5. 对比使用自定义委托和 EventHandler<T> 实现同一个事件,总结各自适用场景
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏