如何使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
相关产品推荐
相关产品推荐

