c#委托和事件

委托

在 C# 中,委托(Delegate) 是一种引用类型,它可以用来封装一个或多个方法,使得方法可以像变量一样被传递、存储和调用。简单来说,委托相当于一个 "方法的容器",允许你将方法作为参数传递给其他方法,或者在不同地方动态调用方法。

委托的基本特性

  1. 类型安全:委托在定义时必须指定它所能封装的方法的签名(参数类型、返回值类型),只能存储与该签名匹配的方法。

  2. 多播性:一个委托实例可以包含多个方法(形成调用列表),调用委托时会依次执行所有包含的方法。

  3. 引用方法:可以引用静态方法,也可以引用实例方法。

委托的使用步骤

1. 定义委托类型

委托的定义类似方法,但需要使用 delegate 关键字,语法如下:

// 定义一个委托类型,它可以封装 "接收两个int参数,返回int" 的方法
public delegate int CalculateDelegate(int a, int b);

这里的委托类型 CalculateDelegate 规定了它能封装的方法必须满足:两个 int 参数,返回 int

2. 创建委托实例并关联方法

定义委托类型后,可以创建其实例,并将匹配签名的方法 "绑定" 到委托上:

// 定义一个与委托签名匹配的方法
public static int Add(int x, int y)
{
    return x + y;
}
​
public static int Multiply(int x, int y)
{
    return x * y;
}
​
// 创建委托实例,并绑定方法
CalculateDelegate calc = new CalculateDelegate(Add);
3. 调用委托

委托实例可以像方法一样被调用,实际会执行它所绑定的方法:

int result = calc(3, 4); // 等价于调用 Add(3,4),结果为 7

委托的多播性(Multicast)

委托可以通过 += 运算符添加多个方法,通过 -= 移除方法,形成一个方法调用列表:

// 添加第二个方法
calc += Multiply;
​
// 调用委托时,会依次执行 Add 和 Multiply
int result = calc(3, 4); 
// 执行顺序:先 Add(3,4)=7,再 Multiply(3,4)=12,最终返回最后一个方法的结果 12

注意:多播委托调用时,返回值为最后一个方法的返回值;如果方法有输出参数,会被最后一个方法覆盖。

内置委托

1. Action 系列

Action 委托用于封装无返回值(void 的方法,可接受 0 到 16 个参数(泛型参数指定参数类型)。

常用形式:
  • Action:无参数的无返回值方法

  • Action<T>:接收 1 个 T 类型参数的无返回值方法

  • Action<T1, T2>:接收 2 个参数的无返回值方法

  • ... 以此类推,最多支持 Action<T1, T2, ..., T16>

示例:
// 无参数
Action printHello = () => Console.WriteLine("Hello");
printHello(); // 输出:Hello
​
// 1 个参数
Action<string> printMessage = (msg) => Console.WriteLine(msg);
printMessage("Action 示例"); // 输出:Action 示例
​
// 2 个参数
Action<int, int> printSum = (a, b) => Console.WriteLine(a + b);
printSum(3, 5); // 输出:8

2. Func 系列

Func 委托用于封装有返回值的方法,最后一个泛型参数表示返回值类型,前面的参数表示输入参数类型,支持 0 到 16 个输入参数。

常用形式:
  • Func<TResult>:无参数,返回 TResult 类型

  • Func<T, TResult>:1 个 T 类型参数,返回 TResult

  • Func<T1, T2, TResult>:2 个参数,返回 TResult

  • ... 最多支持 Func<T1, ..., T16, TResult>

示例:
// 无参数,返回 string
Func<string> getGreeting = () => "Hello Func";
Console.WriteLine(getGreeting()); // 输出:Hello Func
​
// 1 个参数,返回 int(计算字符串长度)
Func<string, int> getLength = (s) => s.Length;
Console.WriteLine(getLength("test")); // 输出:4
​
// 2 个参数,返回 bool(判断是否相等)
Func<int, int, bool> isEqual = (a, b) => a == b;
Console.WriteLine(isEqual(5, 5)); // 输出:True

3. Predicate<T>

Predicate<T> 是一种特殊的泛型委托,用于封装返回 bool 类型的方法,仅接收 1 个 T 类型参数,通常用于判断某个条件是否成立(如集合筛选)。

定义:
public delegate bool Predicate<in T>(T obj);
示例:
// 判断数字是否为偶数
Predicate<int> isEven = (num) => num % 2 == 0;
Console.WriteLine(isEven(4)); // 输出:True
Console.WriteLine(isEven(7)); // 输出:False
​
// 筛选列表中符合条件的元素
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
List<int> evenNumbers = numbers.FindAll(isEven);
// evenNumbers 结果:[2, 4]

4. 事件相关委托

用于定义事件处理方法的委托,遵循 .NET 事件设计规范:

(1)EventHandler

用于无自定义数据的事件,签名为:

public delegate void EventHandler(object sender, EventArgs e);
  • sender:事件源(触发事件的对象)

  • e:事件数据(通常用 EventArgs.Empty 表示无数据)

(2)EventHandler<TEventArgs>

用于带自定义数据的事件(泛型版本),签名为:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) 
    where TEventArgs : EventArgs;
  • TEventArgs:继承自 EventArgs 的自定义事件数据类

示例:
// 自定义事件数据
public class ValueChangedEventArgs : EventArgs
{
    public int OldValue { get; }
    public int NewValue { get; }
    public ValueChangedEventArgs(int oldVal, int newVal)
    {
        OldValue = oldVal;
        NewValue = newVal;
    }
}
​
// 发布者类
public class Counter
{
    private int _value;
    // 声明事件(使用泛型EventHandler)
    public event EventHandler<ValueChangedEventArgs> ValueChanged;
    
    public int Value
    {
        get => _value;
        set
        {
            if (_value != value)
            {
                // 触发事件
                ValueChanged?.Invoke(this, new ValueChangedEventArgs(_value, value));
                _value = value;
            }
        }
    }
}
​
// 使用
var counter = new Counter();
// 订阅事件
counter.ValueChanged += (sender, e) => 
    Console.WriteLine($"值从 {e.OldValue} 变为 {e.NewValue}");
​
counter.Value = 10; // 输出:值从 0 变为 10
counter.Value = 20; // 输出:值从 10 变为 20

内置委托的优势

  1. 减少代码冗余:无需重复定义相同签名的委托,直接使用内置类型。

  2. 增强代码可读性:开发者对内置委托的行为有共识,降低理解成本。

  3. 适配框架 API:.NET 框架中的很多方法(如 LINQ、事件机制)都依赖这些内置委托,使用它们能更好地与框架集成。

示例:使用委托实现动态计算

using System;
​
// 定义委托类型
public delegate int CalculateDelegate(int a, int b);
​
class Program
{
    static int Add(int x, int y) => x + y;
    static int Subtract(int x, int y) => x - y;
    static int Multiply(int x, int y) => x * y;
​
    // 接收委托作为参数的方法
    static void CalculateAndPrint(CalculateDelegate calc, int a, int b)
    {
        int result = calc(a, b);
        Console.WriteLine($"结果:{result}");
    }
​
    static void Main()
    {
        // 动态选择不同的计算方法
        CalculateAndPrint(Add, 10, 5);      // 结果:15
        CalculateAndPrint(Subtract, 10, 5); // 结果:5
        CalculateAndPrint(Multiply, 10, 5); // 结果:50
​
        // 使用 lambda 表达式直接定义方法
        CalculateAndPrint((a, b) => a / b, 10, 5); // 结果:2
    }
}

总结

委托是 C# 中实现方法回调、事件机制的核心机制,它使得方法可以像数据一样被传递和操作,极大地增强了代码的灵活性和扩展性。实际开发中,推荐优先使用 ActionFunc 等系统委托,减少自定义委托的数量

事件

在 C# 中,事件(Event)的实现完全依赖于委托(Delegate),事件本质上是对委托的一种封装和约束,而 事件相关委托 则是专门用于定义事件处理方法签名的委托类型。

.NET 框架为事件处理提供了标准化的委托类型,遵循统一的设计规范,确保事件机制的一致性和易用性。

事件相关委托的核心规范

按照 .NET 设计约定,事件处理委托通常满足以下特征:

  1. 返回值为 void(事件通知通常不需要返回值)

  2. 第一个参数为 object sender:表示触发事件的源头对象(事件发布者)

  3. 第二个参数为 EventArgs 类型或其派生类:用于传递事件相关的数据

常用的事件相关委托

1. EventHandler(无自定义数据)

最基础的事件委托,用于不需要传递自定义数据的事件,定义如下:

public delegate void EventHandler(object sender, EventArgs e);
  • 参数说明:

    • sender:事件的触发者(发布者实例)

    • e:事件数据,无数据时使用 EventArgs.Empty

2. EventHandler<TEventArgs>(带自定义数据)

泛型版本,用于需要传递自定义数据的事件,定义如下:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
    where TEventArgs : EventArgs; // 约束:必须继承自EventArgs
  • TEventArgs:自定义事件数据类,需继承 EventArgs,用于携带事件相关信息

完整使用示例

步骤 1:定义自定义事件数据(可选)

如果需要传递自定义数据,创建继承自 EventArgs 的类:

// 自定义事件数据:存储价格变动信息
public class PriceChangedEventArgs : EventArgs
{
    public decimal OldPrice { get; }
    public decimal NewPrice { get; }
    public DateTime ChangeTime { get; }
​
    public PriceChangedEventArgs(decimal oldPrice, decimal newPrice)
    {
        OldPrice = oldPrice;
        NewPrice = newPrice;
        ChangeTime = DateTime.Now;
    }
}
步骤 2:定义事件发布者(使用事件委托)

在发布者类中声明事件,类型为 EventHandlerEventHandler<TEventArgs>

public class Product
{
    private decimal _price;
​
    // 声明事件(使用带自定义数据的泛型委托)
    public event EventHandler<PriceChangedEventArgs> PriceChanged;
​
    public string Name { get; set; }
    public decimal Price
    {
        get => _price;
        set
        {
            if (_price != value)
            {
                // 触发事件前,先检查是否有订阅者
                OnPriceChanged(_price, value);
                _price = value;
            }
        }
    }
​
    // 封装事件触发逻辑(约定命名为 OnXXX)
    protected virtual void OnPriceChanged(decimal oldPrice, decimal newPrice)
    {
        // 触发事件(使用 ?. 确保有订阅者时才调用)
        PriceChanged?.Invoke(this, new PriceChangedEventArgs(oldPrice, newPrice));
    }
}
步骤 3:定义事件订阅者(实现事件处理方法)

订阅者通过匹配委托签名的方法来响应事件:

public class Customer
{
    public string Name { get; set; }
​
    // 事件处理方法(签名必须与EventHandler<T>匹配)
    public void OnProductPriceChanged(object sender, PriceChangedEventArgs e)
    {
        // 从sender获取事件源信息
        var product = sender as Product;
        
        Console.WriteLine($"{Name} 收到通知:");
        Console.WriteLine($"商品 {product?.Name} 价格变动:");
        Console.WriteLine($"从 {e.OldPrice:C} 变为 {e.NewPrice:C}");
        Console.WriteLine($"变动时间:{e.ChangeTime:yyyy-MM-dd HH:mm:ss}\n");
    }
}
步骤 4:订阅和触发事件
class Program
{
    static void Main()
    {
        // 创建发布者和订阅者
        var laptop = new Product { Name = "笔记本电脑", Price = 5999m };
        var customer1 = new Customer { Name = "张三" };
        var customer2 = new Customer { Name = "李四" };
​
        // 订阅事件(+= 注册处理方法)
        laptop.PriceChanged += customer1.OnProductPriceChanged;
        laptop.PriceChanged += customer2.OnProductPriceChanged;
​
        // 修改价格,触发事件
        laptop.Price = 5499m;
​
        // 取消订阅(-= 移除处理方法)
        laptop.PriceChanged -= customer1.OnProductPriceChanged;
        Console.WriteLine("张三取消订阅后:");
        laptop.Price = 4999m;
    }
}

输出结果

张三 收到通知:
商品 笔记本电脑 价格变动:
从 ¥5,999.00 变为 ¥5,499.00
变动时间:2023-10-01 15:30:00
​
李四 收到通知:
商品 笔记本电脑 价格变动:
从 ¥5,999.00 变为 ¥5,499.00
变动时间:2023-10-01 15:30:00
​
张三取消订阅后:
李四 收到通知:
商品 笔记本电脑 价格变动:
从 ¥5,499.00 变为 ¥4,999.00
变动时间:2023-10-01 15:30:01

事件委托的关键作用

  1. 约束签名:确保事件处理方法遵循统一的参数规范(sender + 事件数据),便于框架和开发者理解。

  2. 解耦通信:发布者只需通过委托触发事件,无需知道订阅者的具体实现。

  3. 安全封装:事件通过委托实现多播(多个订阅者),但外部只能订阅 / 取消订阅,不能直接触发事件(只能在发布者内部触发)。

总结

事件相关委托(EventHandlerEventHandler<TEventArgs>)是 C# 事件机制的基础,它们定义了事件处理方法的标准签名,确保了事件系统的规范性和灵活性。

实际开发中,应始终遵循 .NET 事件设计规范:

  • 事件委托使用 EventHandler 或其泛型版本

  • 事件数据类继承 EventArgs

  • 事件触发方法命名为 OnXXX 并保护(protected

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张謹礧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值