C# 之委托与事件详解

C# 委托与事件详解剖析

委托和事件是C#中实现松耦合和响应式编程的核心机制,本文将全面深入这两个重要概念。

一、委托(Delegate)深入解析

1. 委托的本质

委托实质上是一个类型安全的函数指针,它定义了方法的签名:

// 声明委托类型
public delegate int MathOperation(int a, int b);

class Program
{
    static int Add(int x, int y) => x + y;
    static int Multiply(int x, int y) => x * y;

    static void Main()
    {
        // 创建委托实例
        MathOperation operation = Add;
      
        // 调用委托
        int result = operation(3, 5);  // 8
      
        // 更改委托引用
        operation = Multiply;
        result = operation(3, 5);  // 15
      
        // 多播委托
        operation += Add;
        result = operation(3, 5);  // 只返回最后一个方法的结果(Multiply)
    }
}

2. 委托的高级特性

泛型委托

.NET提供了内置泛型委托,避免自己声明:

// Func - 无参有返回值
Func<string> getTime = () => DateTime.Now.ToString();

// Func - 多参有返回值
Func<int, int, int> addFunc = (x, y) => x + y;

// Action - 无返回值
Action<string> logAction = msg => Console.WriteLine(msg);

// Predicate - 返回bool
Predicate<int> isEven = num => num % 2 == 0;
协变与逆变(C# 4.0+)
// 协变(out) - 返回类型可以是派生类
delegate TResult Func<out TResult>();
Func<string> stringFunc = () => "Hello";
Func<object> objectFunc = stringFunc;  // 合法

// 逆变(in) - 参数类型可以是基类
delegate void Action<in T>(T arg);
Action<object> objectAction = obj => Console.WriteLine(obj);
Action<string> stringAction = objectAction;  // 合法

二、事件(Event)机制剖析

1. 事件的基础模式

事件是基于委托的发布-订阅模型:

public class TemperatureMonitor
{
    // 1. 定义委托类型
    public delegate void TemperatureChangedHandler(decimal newTemperature);
  
    // 2. 声明事件
    public event TemperatureChangedHandler TemperatureChanged;
  
    private decimal _currentTemp;
  
    public decimal CurrentTemperature
    {
        get => _currentTemp;
        set
        {
            if (_currentTemp != value)
            {
                _currentTemp = value;
                // 3. 触发事件
                OnTemperatureChanged(_currentTemp);
            }
        }
    }
  
    // 4. 受保护的虚方法触发事件(标准模式)
    protected virtual void OnTemperatureChanged(decimal newTemp)
    {
        TemperatureChanged?.Invoke(newTemp);
    }
}

// 使用
var monitor = new TemperatureMonitor();
monitor.TemperatureChanged += temp => 
    Console.WriteLine($"温度变化: {temp}°C");

monitor.CurrentTemperature = 23.5m;  // 触发事件

2. 事件访问器(Add/Remove)

自定义事件订阅逻辑:

public class EventSource
{
    private EventHandler _myEvent;
  
    public event EventHandler MyEvent
    {
        add
        {
            Console.WriteLine($"添加订阅者: {value.Method.Name}");
            _myEvent += value;
        }
        remove 
        {
            Console.WriteLine($"移除订阅者: {value.Method.Name}");
            _myEvent -= value;
        }
    }
  
    public void RaiseEvent() => _myEvent?.Invoke(this, EventArgs.Empty);
}

三、委托与事件的进阶应用

1. 异步委托调用

Func<int, int, int> longRunningOp = (a, b) => {
    Thread.Sleep(2000);
    return a + b;
};

// BeginInvoke/EndInvoke模式(传统异步)
IAsyncResult result = longRunningOp.BeginInvoke(3, 5, null, null);
int sum = longRunningOp.EndInvoke(result);  // 阻塞直到完成

// Task封装(现代方式)
Task<int> task = Task.Run(() => longRunningOp(3, 5));
task.ContinueWith(t => Console.WriteLine($"结果为: {t.Result}"));

2. 弱事件模式(Weak Event)

解决内存泄漏问题:

// 使用WeakEventManager
public class NewsPublisher
{
    public event EventHandler<NewsEventArgs> NewsPublished;
  
    public void Publish(string headline)
    {
        NewsPublished?.Invoke(this, new NewsEventArgs(headline));
    }
}

public class NewsEventArgs : EventArgs
{
    public string Headline { get; }
    public NewsEventArgs(string headline) => Headline = headline;
}

// 订阅方(可能会被GC回收)
public class NewsSubscriber
{
    public NewsSubscriber(NewsPublisher publisher)
    {
        WeakEventManager<NewsPublisher, NewsEventArgs>
            .AddHandler(publisher, "NewsPublished", OnNewsPublished);
    }
  
    private void OnNewsPublished(object sender, NewsEventArgs e)
    {
        Console.WriteLine($"收到新闻: {e.Headline}");
    }
}

四、LINQ与委托

LINQ重度依赖委托:

List<Product> products = GetProducts();

// Where接受Predicate委托
var cheapProducts = products.Where(p => p.Price < 50);

// Select使用Func委托
var names = products.Select(p => p.Name);

// 自定义委托链
Func<Product, bool> filter = p => p.Price > 20;
filter += p => p.Stock > 0;

// 应用多条件筛选
var filtered = products.Where(p => filter.GetInvocationList()
                             .All(f => ((Func<Product,bool>)f)(p)));

五、实战设计模式应用

1. 观察者模式(Observer Pattern)

// 使用事件实现
public class Subject
{
    public event EventHandler StateChanged;
  
    private string _state;
  
    public string State
    {
        get => _state;
        set
        {
            _state = value;
            StateChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}

public class Observer
{
    public Observer(Subject subject)
    {
        subject.StateChanged += OnSubjectStateChanged;
    }
  
    private void OnSubjectStateChanged(object sender, EventArgs e)
    {
        Console.WriteLine($"主题状态已更改为: {((Subject)sender).State}");
    }
}

2. 命令模式(Command Pattern)

public interface ICommand
{
    void Execute();
    void Undo();
}

public class DelegateCommand : ICommand
{
    private readonly Action _execute;
    private readonly Action _undo;
  
    public DelegateCommand(Action execute, Action undo)
    {
        _execute = execute;
        _undo = undo;
    }
  
    public void Execute() => _execute();
    public void Undo() => _undo();
}

// 使用
var command = new DelegateCommand(
    execute: () => Console.WriteLine("执行操作"),
    undo: () => Console.WriteLine("撤销操作")
);

command.Execute();

六、性能优化与最佳实践

1. 委托性能考量

  • 缓存委托实例:避免重复创建相同委托

    private static readonly Func<int, int, int> AddFunc = (a, b) => a + b;
    
  • 避免装箱:值类型参数的委托调用会导致装箱

    // 不好:会有装箱
    Action<object> logObject = obj => Console.WriteLine(obj);
    logObject(42);
    
    // 更好:使用泛型避免装箱
    Action<int> logInt = num => Console.WriteLine(num);
    logInt(42);
    

2. 事件最佳实践

  1. 遵循标准模式

    public event EventHandler<MyEventArgs> MyEvent;
    
    protected virtual void OnMyEvent(MyEventArgs e)
    {
        MyEvent?.Invoke(this, e);
    }
    
  2. 线程安全调用

    EventHandler temp = MyEvent;
    if (temp != null)
    {
        temp(this, EventArgs.Empty);
    }
    
  3. 命名规范

    • 委托类型:[Name]EventHandler
    • 事件参数:[Name]EventArgs
    • 引发方法:On[EventName]

七、C#最新特性

1. 本地函数 vs 委托(C# 7.0+)

public void ProcessData()
{
    // 委托方式
    Func<int, int> square = x => x * x;
  
    // 本地函数方式(性能更好)
    int SquareLocal(int x) => x * x;
  
    // 两者都可作为参数传递
    Calculate(square);
    Calculate(SquareLocal);
}

void Calculate(Func<int, int> operation)
{
    Console.WriteLine(operation(5));
}

2. 事件处理简化(C# 9.0+)

public class Timer
{
    public event Action<string> Tick;
  
    public void Start()
    {
        // 使用lambda直接处理事件
        System.Threading.Timer timer = new(_ => 
        {
            Tick?.Invoke($"Tick at {DateTime.Now:T}");
        }, null, 0, 1000);
    }
}

通过深入理解委托和事件,开发者可以构建松耦合、响应式的应用程序架构。这些特性特别适用于GUI编程、插件系统、异步编程等场景,是高级C#开发必备的核心技能。

你可能感兴趣的:(《C#学习笔记》,c#,开发语言)