You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使C#的CustomEventHandler泛型委托支持协变且保留CustomEventArgs泛型

解决C#协变委托与CustomEventArgs的兼容性问题

你提供的代码无法编译,核心原因是:泛型委托声明协变(out T)时,所有涉及T的参数必须处于输出位置,而具体泛型类CustomEventArgs<T>不支持协变,导致T出现在输入位置,违反协变规则。即便类内部用了协变的IReadOnlyList<T>,也无法让整个类具备协变能力。

下面给出满足需求的实现方案:

方案:给CustomEventArgs添加协变泛型接口

通过定义一个支持协变的泛型接口,让委托参数指向这个接口,即可实现委托的协变,同时保留CustomEventArgs<T>的泛型T。

完整代码示例

// 定义协变泛型接口,用out标记T支持协变
public interface ICustomEventArgs<out T>
{
    IReadOnlyList<T> Items { get; }
}

// 原CustomEventArgs<T>实现该协变接口
public class CustomEventArgs<T> : EventArgs, ICustomEventArgs<T>
{
    public IReadOnlyList<T> Items { get; }

    // 补充构造函数初始化Items
    public CustomEventArgs(IReadOnlyList<T> items)
    {
        Items = items;
    }
}

// 委托参数改为协变接口类型,保留out T的协变声明
public delegate void CustomEventHandler<out T>(object? sender, ICustomEventArgs<T> e);

协变使用示例

此时委托可以正常进行协变转换,比如子类类型的委托可以赋值给父类类型的委托:

// 处理object类型事件的委托
CustomEventHandler<object> objectHandler = (sender, args) => 
{
    Console.WriteLine($"收到{args.Items.Count}个对象");
};

// 协变转换:string是object的子类,可直接赋值
CustomEventHandler<string> stringHandler = objectHandler;

// 触发事件时传入CustomEventArgs<string>
stringHandler.Invoke(null, new CustomEventArgs<string>(new List<string>{"a", "b"}));

原理说明

C#的变体规则只适用于泛型接口和泛型委托,具体泛型类无法声明out/in变体标记。通过将CustomEventArgs<T>的核心成员抽象到协变接口ICustomEventArgs<out T>,委托的参数类型变为协变接口,此时T处于接口的输出位置(只读属性),符合协变的要求,编译器就会允许委托声明out T。

内容的提问来源于stack exchange,提问作者ashbygeek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 17:20:14