×

C# 委托与事件:日常最容易被低估的强大特性

独孤求败 独孤求败 发表于2026-04-02 11:07:12 浏览22 评论0

抢沙发发表评论

引言

C# 中的两个强大特性:委托和事件。它们就像瑞士军刀一样,小巧玲珑却功能强大,在各种编程场景中都能发挥重要作用。

无论你是刚接触 C# 的新手,还是有一定经验的开发者,理解委托和事件都是掌握 C# 核心编程思想的关键。本文将通过通俗易懂的语言和实用的代码示例,帮助你轻松掌握这一技术。

委托:方法的「容器」

1. 什么是委托?

简单来说,委托就是一个「方法容器」,它可以存储对方法的引用。想象一下,你有一个盒子,里面可以放各种形状相同的工具,委托就像这个盒子,它可以装下所有签名相同的方法。

// 定义一个委托类型
delegate void MyDelegate(string message)
;

class
 Program
{
    static void Main()

    {
        // 创建委托实例并指向一个方法

        MyDelegate del = new MyDelegate(PrintMessage);
        
        // 调用委托

        del("Hello, Delegate!");
    }
    
    static void PrintMessage(string message)

    {
        Console.WriteLine(message);
    }
}

2. 委托的特点

  • 类型安全:编译时会检查方法签名是否匹配,避免运行时错误
  • • 多播能力:一个委托可以指向多个方法,形成调用链
  • • 可传递性:委托可以作为参数传递给其他方法
  • • 返回值:委托可以有返回值,多播时只返回最后一个方法的结果

3. 泛型委托:更简洁的写法

C# 提供了一些预定义的泛型委托,让代码更简洁:

  • • Action:无返回值的委托
  • • Func:有返回值的委托
  • • Predicate:返回布尔值的委托
// Action 委托:无返回值
Action<string> action = (message) => Console.WriteLine(message);
action("Hello, Action!");

// Func 委托:有返回值

Func<int, int, int> add = (a, b) => a + b;
int
 result = add(10, 20);
Console.WriteLine(result);

// Predicate 委托:返回布尔值

Predicate<int> isEven = (num) => num % 2 == 0;
bool
 result = isEven(4);
Console.WriteLine(result);

事件:对象间的「消息传递员」

1. 什么是事件?

事件是基于委托的一种机制,它实现了「发布-订阅」模式。想象一下,你订阅了一份报纸,当有新内容时,报社就会把报纸送到你手上。事件就是这样:当某个对象发生特定事情时,它会通知所有订阅了这个事件的对象。

class Publisher
{
    // 定义事件

    public
 event Action<string> MessagePublished;
    
    public void Publish(string message)

    {
        // 触发事件

        MessagePublished?.Invoke(message);
    }
}

class
 Subscriber
{
    public Subscriber(Publisher publisher)

    {
        // 订阅事件

        publisher.MessagePublished += OnMessagePublished;
    }
    
    private void OnMessagePublished(string message)

    {
        Console.WriteLine($"Received: {message}");
    }
}

class
 Program
{
    static void Main()

    {
        Publisher publisher = new Publisher();
        Subscriber subscriber = new Subscriber(publisher);
        
        publisher.Publish("Hello, Event!");
    }
}

2. 事件的特点

  • • 封装性:事件只能在定义它的类内部触发,外部代码无法直接触发
  • • 订阅/取消订阅:使用 += 订阅事件,使用 -= 取消订阅
  • • 线程安全:事件的触发是线程安全的
  • • 委托基础:事件本质上是对委托的封装,提供了更安全的访问控制

委托与事件的关系

1. 事件是委托的「安全封装」

事件可以看作是委托的一个特例,它添加了额外的访问控制:

  • • 外部代码只能订阅或取消订阅事件,不能直接触发它
  • • 外部代码不能直接修改事件的委托链

2. 委托与事件的区别

特性
委托
事件
访问权限
可以在任何地方调用
只能在定义它的类内部调用
赋值操作
可以直接赋值 (=)
只能使用 += 和 -=
用途
一般用于回调
一般用于事件通知

实际应用场景

1. 回调函数:任务完成后的「通知」

委托最基本的用途是作为回调函数,当一个任务完成后,自动执行指定的方法:

void ProcessData(string data, Action<string> callback)
{
    // 处理数据

    string
 result = data.ToUpper();
    
    // 调用回调

    callback(result);
}

// 使用

ProcessData("hello", (result) => Console.WriteLine(result));

2. 事件驱动编程:响应用户操作

在 GUI 编程中,事件用于处理用户交互,比如按钮点击、鼠标移动等:

// Windows Forms 示例
button1.Click += (sender, e) => {
    MessageBox.Show("Button clicked!");
};

3. 观察者模式:状态变化的「广播」

事件是实现观察者模式的理想选择,当一个对象的状态发生变化时,通知所有关注它的对象:

class Stock
{
    public
 event Action<decimal> PriceChanged;
    
    private
 decimal price;
    public
 decimal Price
    {
        get
 { return price; }
        set

        {
            if
 (price != value)
            {
                price = value;
                PriceChanged?.Invoke(price);
            }
        }
    }
}

class
 Investor
{
    public
 string Name { get; set; }
    
    public Investor(string name, Stock stock)

    {
        Name = name;
        stock.PriceChanged += OnPriceChanged;
    }
    
    private void OnPriceChanged(decimal price)

    {
        Console.WriteLine($"{Name} notified: Stock price is now {price}");
    }
}

高级用法

1. 异步委托:不阻塞主线程

委托可以用于异步操作,让耗时任务在后台执行,不阻塞主线程:

Func<int, int, int> add = (a, b) => {
    Thread.Sleep(1000); // 模拟耗时操作
    return
 a + b;
};

// 异步调用

IAsyncResult result = add.BeginInvoke(10, 20, null, null);

// 做其他事情

Console.WriteLine("Doing other work...");

// 获取结果

int
 sum = add.EndInvoke(result);
Console.WriteLine($"Sum: {sum}");

2. 委托的组合与移除:灵活管理方法链

Action<string> action1 = (msg) => Console.WriteLine($"Action1: {msg}");
Action<string> action2 = (msg) => Console.WriteLine($"Action2: {msg}");

// 组合委托

Action<string> combined = action1 + action2;
combined("Hello");

// 移除委托

combined -= action1;
combined("Hello");

3. 自定义事件访问器:更精细的控制

对于复杂场景,我们可以自定义事件的访问器,实现更精细的控制:

class CustomEventPublisher
{
    private
 Action<string> messageHandlers;
    
    public
 event Action<string> MessagePublished
    {
        add

        {
            Console.WriteLine("A subscriber added");
            messageHandlers += value;
        }
        remove

        {
            Console.WriteLine("A subscriber removed");
            messageHandlers -= value;
        }
    }
    
    public void Publish(string message)

    {
        messageHandlers?.Invoke(message);
    }
}

最佳实践

1. 委托使用建议

  • • 对于简单的回调,优先使用 Action 或 Func 泛型委托
  • • 对于复杂的场景,定义专用的委托类型,提高代码可读性
  • • 避免在委托链中包含耗时操作,以免影响性能

2. 事件使用建议

  • • 始终使用 EventHandler 或 EventHandler<TEventArgs> 作为事件委托类型
  • • 遵循 .NET 事件命名约定:OnEventName 作为触发方法,EventName 作为事件名称
  • • 在触发事件前检查是否为 null(使用 ?. 操作符)
  • • 避免在事件处理程序中抛出异常,以免影响其他订阅者

3. 性能考虑

  • • 对于高频事件,考虑使用弱事件模式,避免内存泄漏
  • • 对于大型委托链,考虑使用事件聚合器模式,提高管理效率
  • • 避免在事件处理程序中执行耗时操作,考虑使用异步处理

常见陷阱

1. 内存泄漏:不要让对象「赖着不走」

如果订阅者对象的生命周期比发布者长,可能会导致内存泄漏:

// 不好的做法
public
 class BadSubscriber
{
    public BadSubscriber(Publisher publisher)

    {
        // 匿名方法捕获了 this,导致 publisher 持有对 subscriber 的引用

        publisher.MessagePublished += (message) => {
            Console.WriteLine($"{this.GetType().Name} received: {message}");
        };
    }
}

// 好的做法:使用弱引用或明确取消订阅

public
 class GoodSubscriber : IDisposable
{
    private
 Publisher publisher;
    
    public GoodSubscriber(Publisher publisher)

    {
        this
.publisher = publisher;
        publisher.MessagePublished += OnMessagePublished;
    }
    
    private void OnMessagePublished(string message)

    {
        Console.WriteLine($"{GetType().Name} received: {message}");
    }
    
    public void Dispose()

    {
        publisher.MessagePublished -= OnMessagePublished;
    }
}

2. 线程安全:多线程环境下的注意事项

在多线程环境中,事件触发需要注意线程安全:

// 线程安全的事件触发
public
 event Action<string> MessagePublished;

protected virtual void OnMessagePublished(string message)

{
    // 复制到局部变量,避免在触发过程中委托被修改

    Action<string> handler = MessagePublished;
    handler?.Invoke(message);
}

代码示例

完整的委托与事件示例

using System;

namespace
 DelegateAndEventSamples
{
    // 基本委托与事件示例相关类型

    delegate void MessageHandler(string message)
;
    
    class
 Publisher
    {
        // 定义事件

        public
 event MessageHandler? MessageSent;
        
        public void SendMessage(string message)

        {
            Console.WriteLine($"[Publisher] 发送消息: {message}");
            // 触发事件

            OnMessageSent(message);
        }
        
        protected virtual void OnMessageSent(string message)

        {
            Console.WriteLine("[Publisher] 触发 MessageSent 事件");
            MessageSent?.Invoke(message);
        }
    }
    
    class
 Subscriber
    {
        private
 string name;
        
        public Subscriber(string name, Publisher publisher)

        {
            this
.name = name;
            // 订阅事件

            Console.WriteLine($"[Subscriber] {name} 订阅了 MessageSent 事件");
            publisher.MessageSent += ReceiveMessage;
        }
        
        private void ReceiveMessage(string message)

        {
            Console.WriteLine($"[Subscriber] {name} 收到消息: {message}");
        }
    }
    
    class
 Program
    {
        static void Main(string[] args)

        {
            // 创建发布者

            Publisher publisher = new Publisher();
            
            // 创建订阅者

            Subscriber subscriber1 = new Subscriber("Subscriber 1", publisher);
            Subscriber subscriber2 = new Subscriber("Subscriber 2", publisher);
            
            // 发送消息

            publisher.SendMessage("Hello, World!");
            
            Console.ReadLine();
        }
    }
}

泛型委托示例

using System;

namespace
 DelegateAndEventSamples
{
    class
 Program
    {
        static void Main(string[] args)

        {
            // Action 委托:无返回值

            Action<string> printAction = (message) => Console.WriteLine($"   执行 Action: {message}");
            printAction("Hello from Action!");
            
            // Func 委托:有返回值

            Func<int, int, int> addFunc = (a, b) => 
            {
                int
 result = a + b;
                Console.WriteLine($"   执行 Func: {a} + {b} = {result}");
                return
 result;
            };
            int
 sum = addFunc(10, 20);
            
            // Predicate 委托:返回布尔值

            Predicate<int> isPositive = (num) => 
            {
                bool
 result = num > 0;
                Console.WriteLine($"   执行 Predicate: {num} 是正数吗? {result}");
                return
 result;
            };
            bool
 result = isPositive(5);
            
            Console.ReadLine();
        }
    }
}

事件聚合器示例

using System;
using
 System.Collections.Generic;

namespace
 DelegateAndEventSamples
{
    class
 EventAggregator
    {
        private
 readonly Dictionary<Type, List<Delegate>> eventHandlers = new Dictionary<Type, List<Delegate>>();
        
        public void Subscribe<T>(Action<T> handler)

        {
            Type eventType = typeof(T);
            if
 (!eventHandlers.ContainsKey(eventType))
            {
                eventHandlers[eventType] = new List<Delegate>();
                Console.WriteLine($"[EventAggregator] 为 {eventType.Name} 类型创建事件处理器列表");
            }
            eventHandlers[eventType].Add(handler);
            Console.WriteLine($"[EventAggregator] 订阅 {eventType.Name} 类型的事件");
        }
        
        public void Unsubscribe<T>(Action<T> handler)

        {
            Type eventType = typeof(T);
            if
 (eventHandlers.ContainsKey(eventType))
            {
                eventHandlers[eventType].Remove(handler);
                Console.WriteLine($"[EventAggregator] 取消订阅 {eventType.Name} 类型的事件");
            }
        }
        
        public void Publish<T>(T eventData)

        {
            Type eventType = typeof(T);
            Console.WriteLine($"[EventAggregator] 发布 {eventType.Name} 类型的事件");
            if
 (eventHandlers.ContainsKey(eventType))
            {
                Console.WriteLine($"[EventAggregator] 找到 {eventHandlers[eventType].Count} 个事件处理器");
                foreach
 (Action<T> handler in eventHandlers[eventType])
                {
                    handler(eventData);
                }
            }
            else

            {
                Console.WriteLine($"[EventAggregator] 没有找到 {eventType.Name} 类型的事件处理器");
            }
        }
    }
    
    // 事件数据类

    class
 MessageEvent
    {
        public
 string Message { get; set; } = string.Empty;
    }
    
    class
 Program
    {
        static void Main(string[] args)

        {
            EventAggregator aggregator = new EventAggregator();
            
            // 订阅事件

            aggregator.Subscribe<MessageEvent>((e) => Console.WriteLine($"   事件处理器: 收到消息: {e.Message}"));
            
            // 发布事件

            aggregator.Publish(new MessageEvent { Message = "Hello from EventAggregator!" });
            
            Console.ReadLine();
        }
    }
}

整合示例程序

我们还创建了一个整合了所有示例的控制台应用程序,使用选项式菜单系统让你可以方便地运行和查看所有示例:

using System;
using
 System.Collections.Generic;

namespace
 DelegateAndEventSamples
{
    
    class
 Program
    {
        static void Main(string[] args)

        {
            bool
 exit = false;
            
            while
 (!exit)
            {
                Console.Clear();
                Console.WriteLine("==================================");
                Console.WriteLine("C# 委托与事件示例程序");
                Console.WriteLine("==================================");
                Console.WriteLine("1. 基本委托与事件示例");
                Console.WriteLine("2. 泛型委托示例");
                Console.WriteLine("3. 事件聚合器示例");
                Console.WriteLine("4. 退出程序");
                Console.WriteLine("==================================");
                Console.Write("请选择示例编号: ");
                
                string
 input = Console.ReadLine();
                Console.WriteLine();
                
                switch
 (input)
                {
                    case
 "1":
                        RunDelegateAndEventExample();
                        break
;
                    case
 "2":
                        RunGenericDelegateExample();
                        break
;
                    case
 "3":
                        RunEventAggregatorExample();
                        break
;
                    case
 "4":
                        exit = true;
                        break
;
                    default
:
                        Console.WriteLine("无效的选择,请重新输入。");
                        break
;
                }
                
                if
 (!exit)
                {
                    Console.WriteLine("\n按回车键返回菜单...");
                    Console.ReadLine();
                }
            }
            
            Console.WriteLine("程序已退出。");
        }
        
    }
}

示例运行效果如下:

图片

总结

委托和事件是 C# 中非常强大的特性,它们为我们提供了一种灵活的方式来实现回调、通知和事件驱动编程。就像瑞士军刀一样,它们在各种场景下都能发挥重要作用,从简单的回调到复杂的事件系统,都能轻松应对。

在实际开发中,合理使用委托和事件可以让你的代码更加灵活、可维护,同时也能更好地实现模块化和松耦合的设计。

希望本文对你有所帮助,祝你在 C# 编程之路上越走越远!


群贤毕至

访客