一尘不染

使用泛型的C#协变量返回类型

c#

下面的代码是实现协变返回类型的唯一方法吗?

public abstract class BaseApplication<T> {
    public T Employee{ get; set; }
}

public class Application : BaseApplication<ExistingEmployee> {}

public class NewApplication : BaseApplication<NewEmployee> {}

我希望能够构造一个Application或NewApplication,并使其从Employee属性返回适当的Employee类型。

var app = new Application();
var employee = app.Employee; // this should be of type ExistingEmployee

我相信这段代码可以正常工作,但是当我有多个需要相同行为的属性时,它确实很讨厌。

还有其他方法可以实现此行为吗?泛型还是其他?


阅读 274

收藏
2020-05-19

共1个答案

一尘不染

首先,您的问题的答案是否定的,C#不支持任何形式的虚拟覆盖类型的返回类型协方差。

许多回答者和评论者说“这个问题没有协方差”。这是不正确的。原始海报完全像他们一样提出问题。

回想一下,协变映射是保留了某些其他关系的存在和方向的映射。例如,从类型T到类型的映射IEnumerable<T>是协变的,因为它保留了分配兼容性关系。如果Tiger与Animal分配兼容,则地图下的变换也将保留:IEnumerable<Tiger>分配与兼容IEnumerable<Animal>

这里的协变映射很难看,但仍然存在。本质上的问题是:这是否合法?

class B
{
    public virtual Animal M() {...}
}
class D : B
{
    public override Tiger M() {...}
}

老虎与动物兼容。现在,从类型T映射到方法“ public TM()”。 该映射是否保留兼容性 ?也就是说,
如果Tiger出于分配目的与Animal兼容,那么 为了虚拟覆盖而public Tiger M()兼容public Animal M()吗?

C#的答案是“否”。C#不支持这种协方差。

既然我们已经确定问题是使用正确的类型代数术语来提出的,那么对实际问题还有更多的想法。显而易见的第一个问题是,该属性甚至尚未被声明为虚拟属性,因此虚拟兼容性问题尚无定论。显而易见的第二个问题是“获取;设置;设置”;即使C#支持返回类型协方差,property属性也不是协变的,因为
带有setter的属性的类型不仅是其返回类型,而且还是其形式参数类型 。您需要 逆变
正式参数类型,实现类型安全。如果我们允许使用setter的属性返回类型协方差,那么您将:

class B
{
    public virtual Animal Animal{ get; set;}
}
class D : B
{
    public override Tiger Animal { ... }
}

B b = new D();
b.Animal = new Giraffe();

嘿,我们刚刚将一只长颈鹿送给了一只期待老虎的饲养员。如果我们支持此功能,则必须将其限制为返回类型(就像我们对通用接口的赋值-兼容性协方差所做的那样。)

第三个问题是CLR不支持这种差异。如果我们想以这种语言来支持它(就像我相信托管C
++那样),那么我们将不得不采取一些合理的措施来解决CLR中的签名匹配限制。

您可以通过仔细定义具有适合其基类类型的适当返回类型的“新”方法来自己采取这些英勇措施:

abstract class B 
{
    protected abstract Animal ProtectedM();
    public Animal Animal { get { return this.ProtectedM(); } }
}
class D : B
{
    protected override Animal ProtectedM() { return new Tiger(); }
    public new Tiger Animal { get { return (Tiger)this.ProtectedM(); } }
}

现在,如果您有D的实例,您将看到Tiger型的属性。如果将其强制转换为B,则会看到Animal-
typed属性。无论哪种情况,您仍然可以通过受保护的成员获得虚拟行为。

简而言之,很抱歉,我们没有计划使用此功能。

2020-05-19