为什么在 C# 中使用具有继承接口的具体类实例的通用接口的变量失败?

Yan*_*hon 1 c# generics

鉴于这些接口和类

public interface IFoo { }

public interface IFooWrapper<I> where I : IFoo { }

public class Foo : IFoo { }
public class FooWrapper : IFooWrapper<Foo> { }
Run Code Online (Sandbox Code Playgroud)

为什么这会失败?

IFooWrapper<IFoo> foo = new FooWrapper();
Run Code Online (Sandbox Code Playgroud)

我知道我可以dynamic在这里使用,但真正的问题是:我有一个方法可以接收这些接口的实现,但由于同样的原因它失败了:

严重性代码说明项目文件行抑制状态错误 CS0266 无法将类型“FooWrapper”隐式转换为“IFooWrapper”。存在显式转换(您是否缺少演员表?)

方法签名看起来像

void Register(IFooWrapper<IFoo> foo)
{
}
Run Code Online (Sandbox Code Playgroud)

编译器在该行失败

Register(new FooWrapper());
Run Code Online (Sandbox Code Playgroud)

Jul*_*ian 8

那是因为协方差和逆方差。

当某个东西是 a 时IFooWrapper<Foo>,这并不意味着它可以在编译时转换为IFooWrapper<IFoo>. 我认为这里解释得更好:Covariance and Contravariance (C#) | Microsoft Docs并且仍然对 covariance 和 contravariance & in/out 感到困惑

请注意,您可以将 C# 中的接口标记为协变或逆变。例如,如果您有以下接口(请注意out I- 它现在是类型参数的协变I),您可以使用IFooWrapper<IFoo> foo = new FooWrapper();

public interface IFooWrapper<out I> where I : IFoo { }
Run Code Online (Sandbox Code Playgroud)

查看Fiddle 中的演示

您可以使用泛型修复方法签名,而不是更改接口:

void Register<T>(IFooWrapper<T> foo) where T: IFoo
{
}
Run Code Online (Sandbox Code Playgroud)

可以用IFooWrapper<Foo>and调用那个IFooWrapper<IFoo>- 所以这是有效的:Register(new FooWrapper());

另见小提琴