.NET 6中如何在派生类为泛型成员变量指定具体泛型类型
问题场景
现有基类BaseClass,其中包含类型为BaseGeneric<BaseWidget>的抽象属性,派生类DerivedClass尝试为该属性赋值DerivedGeneric实例时,出现编译错误:Cannot implicitly convert type 'DerivedGeneric' to 'BaseGeneric<BaseWidget>'。相关代码如下:
class DerivedClass : BaseClass { // 编译通过 public override BaseWidget Widget => new DerivedWidget(); // 编译通过 public override IEnumerable<object> Objects => (IEnumerable<object>)new List<int>(); // 编译失败 public override BaseGeneric<BaseWidget> Generic => new DerivedGeneric(); // 强制转换也编译失败 public override BaseGeneric<BaseWidget> BonusGeneric => (BaseGeneric<BaseWidget>)new DerivedGeneric(); } abstract class BaseClass { public abstract BaseWidget Widget { get; } public abstract IEnumerable<object> Objects { get; } public abstract BaseGeneric<BaseWidget> Generic { get; } public abstract BaseGeneric<BaseWidget> BonusGeneric { get; } } class BaseWidget { } class DerivedWidget : BaseWidget { } abstract class BaseGeneric<T> where T : BaseWidget { } class DerivedGeneric : BaseGeneric<DerivedWidget> { }
错误原因
默认情况下,泛型类是不变的:即使DerivedWidget继承自BaseWidget,BaseGeneric<DerivedWidget>和BaseGeneric<BaseWidget>之间也不存在继承关系,因此无法直接转换。
而IEnumerable<object>可以接收List<int>,是因为IEnumerable<T>被定义为协变接口(声明为IEnumerable<out T>),协变允许将IEnumerable<派生类>隐式转换为IEnumerable<基类>。但你的BaseGeneric<T>是抽象类,C#不支持泛型类的协变/逆变,只支持接口和委托的协变/逆变。
解决方案
方案1:将BaseGeneric改为协变接口
把抽象类BaseGeneric<T>改为接口,并使用out关键字标记泛型参数为协变(协变要求泛型参数仅作为输出使用,不能作为方法的输入参数):
// 改为协变接口 interface IBaseGeneric<out T> where T : BaseWidget { } class DerivedGeneric : IBaseGeneric<DerivedWidget> { }
然后修改BaseClass中的属性类型:
abstract class BaseClass { public abstract BaseWidget Widget { get; } public abstract IEnumerable<object> Objects { get; } // 将类型改为IBaseGeneric<BaseWidget> public abstract IBaseGeneric<BaseWidget> Generic { get; } public abstract IBaseGeneric<BaseWidget> BonusGeneric { get; } }
此时DerivedClass中的赋值可以直接通过编译,无需强制转换:
public override IBaseGeneric<BaseWidget> Generic => new DerivedGeneric();
方案2:让BaseClass成为泛型类
如果不想将BaseGeneric<T>改为接口,可以让BaseClass引入泛型参数,约束其为BaseWidget的派生类:
// 基类添加泛型参数 abstract class BaseClass<T> where T : BaseWidget { public abstract BaseWidget Widget { get; } public abstract IEnumerable<object> Objects { get; } // 使用泛型参数T代替BaseWidget public abstract BaseGeneric<T> Generic { get; } public abstract BaseGeneric<T> BonusGeneric { get; } } // 派生类指定具体的T为DerivedWidget class DerivedClass : BaseClass<DerivedWidget> { public override BaseWidget Widget => new DerivedWidget(); public override IEnumerable<object> Objects => new List<int>(); // 直接返回DerivedGeneric,类型匹配 public override BaseGeneric<DerivedWidget> Generic => new DerivedGeneric(); public override BaseGeneric<DerivedWidget> BonusGeneric => new DerivedGeneric(); }
这种方式通过让基类绑定具体的Widget类型,避免了跨泛型类型的转换问题。
选择建议
- 如果
BaseGeneric<T>中没有接收T作为参数的方法,优先选择方案1,代码更简洁且符合面向接口的设计原则。 - 如果
BaseGeneric<T>需要保留类的特性(如字段、非抽象方法实现),或存在接收T的方法(无法使用协变),则选择方案2。
内容的提问来源于stack exchange,提问作者jgs1995

