为什么协方差没有按预期工作?

Why is covariance not working as expected?

为什么这不起作用?编译器应该足够聪明,知道 InterfaceB 需要 InterfaceA,因此必须兼容。

public interface InterfaceA
{ }

public interface InterfaceB : InterfaceA
{ }

public abstract class DerivedClass : BaseClass
{
    protected override InterfaceB ItemService { get; set; } // Error, needs to be InterfaceA
}

public abstract class BaseClass
{
    protected virtual InterfaceA ItemService { get; set; }
}

这是为什么?

The compiler should be smart enough to know that InterfaceB requires InterfaceA and therefore must be compatible.

但它不兼容 - 你可以这样做:

DerivedClass derived = new DerivedClass();
InterfaceB ib = new InterfaceBImpl(); 
derived.ItemService = ib;              // good so far
InterfaceA ia = new InterfaceAImpl();  // still good
BaseClass bc = derived;                // still a legal downcast
bc.ItemService = ia;                   // seemingly good - BaseClass can store an InterfaceA
ib = derived.ServiceImpl;

这里是爆炸的地方。您在 属性 中存储了一个未实现 InterfaceB 的对象, 应该 需要 InterfaceB.

做你想做的事情的一种常见方法仍然是泛型:

public abstract class DerivedClass : BaseClass<InterfaceB>
{
    //protected override InterfaceB ItemService { get; set; } // Error, needs to be InterfaceA
    // no override needed - ItemService will now be of type InterfaceB
}

public abstract class BaseClass<T> where T : InterfaceA
{
    protected T ItemService { get; set; }
}

我承认我还没有深入研究 C# 9,但据我所知,协变 returns 仅适用于 方法 只获取 属性。如果是这种情况,您的解决方案就可以了。在这种情况下,是 setter 允许您打破类型系统。

似乎还支持 只读 属性,因此您可以这样做:


public abstract class DerivedClass : BaseClass
{
    protected override InterfaceB ItemService { get; } 
}

public abstract class BaseClass
{
    protected virtual InterfaceA ItemService { get; }
}

基础 class 表示任何 InterfaceA 都可以分配给 ItemService,因此派生 class 不能将其更改为“只能将 InterfaceB 分配给它”。但是如果你放弃作业,那就是:-

  public interface InterfaceB : InterfaceA
    {
    }

    public abstract class DerivedClass : BaseClass
    {
        protected override InterfaceB ItemService { get; } // no set
    }

    public abstract class BaseClass
    {
        protected virtual InterfaceA ItemService { get; }

    }

它会起作用,因为没有分配......但我不确定你的实际目标