关于C#泛型接口继承规则的困惑及相关规则咨询
我完全懂你的困惑!我当初刚接触协变逆变的时候也被这段文档绕晕过,咱们一点点拆开来理清楚:
首先先把你看到的那段文档原话再拎出来:
泛型接口如果是协变的(即仅将其类型参数用作返回值),则可以从非泛型接口继承。在.NET类库中,
IEnumerable<T>继承自IEnumerable,因为IEnumerable<T>仅在GetEnumerator的返回值和Current属性的getter中使用T。
你之前的误解是把“泛型接口继承非泛型接口”的条件和“协变接口的约束”搞混了——文档说的不是“泛型接口要继承非泛型接口,必须让T只做返回值”,而是:
如果你要定义一个协变的泛型接口(也就是用
out修饰类型参数T的那种),这个接口本身必须满足“T只用作返回值、不能用作输入参数”的协变约束;而满足这个约束的协变泛型接口,是允许继承非泛型接口的。
而你测试时写的代码,大概率是没有给泛型接口加out修饰符(也就是非协变的泛型接口),这种情况下,C#对它继承非泛型接口没有任何“T只能做返回值”的限制——哪怕你把T当方法参数用,编译器也会放行,这完全是符合规则的。
接下来给你明确列一下C#里不允许的相关情况,以及适用范围:
一、哪些继承/定义是C#不允许的?
- 协变泛型接口(带
out T)的非法定义:如果你给泛型接口加了out T(声明为协变),但接口里的T被用作了方法的输入参数(比如void Process(T item)),编译器会直接报错——这才是文档里隐含的“不允许”的情况,和继承非泛型接口本身无关,是协变接口自身的约束。 - 逆变泛型接口(带
in T)的非法定义:反过来,逆变接口(in T)要求T只能用作输入参数,不能用作返回值(比如T GetItem()),否则编译器也会报错,同样和继承无关,是逆变自身的规则。 - 注意:如果你的泛型接口既不是协变也不是逆变(没有
in/out修饰符),那不管T是做参数还是返回值,它继承非泛型接口都是完全合法的,编译器不会有任何意见。
二、这个规则适用于类继承吗?
完全不适用!因为协变和逆变的特性只针对接口和委托,C#里的类(不管是泛型类还是非泛型类)都不支持协变/逆变。类的继承只需要遵循正常的C#类继承规则就行——比如不能多继承类、子类要符合基类的访问权限约束等,和“T用作参数还是返回值”没有半毛钱关系。
给你举两个直观的代码例子:
合法情况(非协变泛型接口继承非泛型接口,T用作参数)
public interface INormalInterface { } // 没有out修饰,是非协变泛型接口 public interface IGenericInterface<T> : INormalInterface { void DoSomething(T input); // 完全合法,编译器不会拦 T GetSomething(); }
非法情况(协变泛型接口违反约束)
public interface INormalInterface { } // 声明为协变接口(out T) public interface ICovariantInterface<out T> : INormalInterface { void DoSomething(T input); // 编译器报错!协变接口的T不能用作输入参数 T GetSomething(); // 这个是合法的 }
总结一下:文档的描述是把“协变接口的约束”和“协变接口继承非泛型接口的合法性”放在一起说了,导致你误解了继承的前提。核心是:泛型接口继承非泛型接口本身几乎没有限制,限制只来自于你是否把这个泛型接口声明为协变/逆变,以及是否遵守协变/逆变的自身规则。
内容来源于stack exchange

