问题描述
下面的代码是实现协变返回类型的唯一方法吗?
Is the code below the only way to implement covariant return types?
public abstract class BaseApplication<T> {
public T Employee{ get; set; }
}
public class Application : BaseApplication<ExistingEmployee> {}
public class NewApplication : BaseApplication<NewEmployee> {}
我希望能够构建一个 Application 或一个 NewApplication 并让它从 Employee 属性返回适当的 Employee 类型.
I want to be able to construct an Application or a NewApplication and have it return the appropriate Employee type from the Employee property.
var app = new Application();
var employee = app.Employee; // this should be of type ExistingEmployee
我相信这段代码运行良好,但是当我有多个需要相同行为的属性时,它会变得非常糟糕.
I believe this code works fine, but it gets really nasty when I have several properties that require the same behavior.
还有其他方法可以实现这种行为吗?泛型还是其他?
Are there any other ways to implement this behavior? Generics or otherwise?
推荐答案
UPDATE:这个答案写于 2010 年.经过二十年人们为 C# 提出返回类型协变的建议,看起来它终于要实现了;我比较惊讶.见底部https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ 用于公告;我相信细节会随之而来.下面的答案中推测该功能被实现的可能性的部分应该只考虑到未来的历史意义.
UPDATE: This answer was written in 2010. After two decades of people proposing return type covariance for C#, it looks like it will finally be implemented; I am rather surprised. See the bottom of https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ for the announcement; I'm sure details will follow. The portions of the answer below which speculate on the possibility of the feature being implemented should be considered of historical interest only going forwards.
首先,您的问题的答案是否定的,C# 不支持虚拟覆盖的任何形式的返回类型协变.
First off, the answer to your question is no, C# does not support any form of return type covariance on virtual overrides.
许多回答者和评论者都说这个问题没有协方差".这是不正确的;原始海报完全正确地提出了他们提出的问题.
A number of answerers and commenters have said "there is no covariance in this question". This is incorrect; the original poster was entirely correct to pose the question as they did.
回想一下,协变映射是一种保持其他关系的存在和方向的映射.例如,从类型 T
到类型 IEnumerable
的映射是协变的,因为它保留了赋值兼容性关系.如果Tiger 与Animal 赋值兼容,那么map 下的变换也保留:IEnumerable
与IEnumerable
赋值兼容.
Recall that a covariant mapping is a mapping which preserves the existence and direction of some other relation. For example, the mapping from a type T
to a type IEnumerable<T>
is covariant because it preserves the assignment compatibility relation. If Tiger is assignment compatible with Animal, then the transformation under the map is also preserved: IEnumerable<Tiger>
is assignment compatible with IEnumerable<Animal>
.
这里的协变映射有点难看,但它仍然存在.问题本质上是这样的:这应该合法吗?
The covariant mapping here is a little bit harder to see, but it is still there. The question essentially is this: should this be legal?
class B
{
public virtual Animal M() {...}
}
class D : B
{
public override Tiger M() {...}
}
Tiger 与 Animal 赋值兼容.现在进行从类型 T 到方法public T M()"的映射.该映射是否保持兼容性?也就是说,如果 Tiger 出于赋值目的与 Animal 兼容,那么 public Tiger M()
与 public Animal M()
出于赋值目的兼容虚拟覆盖?
Tiger is assignment-compatible with Animal. Now make a mapping from a type T to a method "public T M()". Does that mapping preserve compatibility? That is, if Tiger is compatible with Animal for the purposes of assignment, then is public Tiger M()
compatible with public Animal M()
for the purposes of virtual overriding?
C# 中的答案是不".C# 不支持这种协方差.
The answer in C# is "no". C# does not support this kind of covariance.
既然我们已经确定问题是使用正确的类型代数术语提出的,那么对实际问题还有一些想法.显而易见的第一个问题是该属性甚至还没有被声明为虚拟的,因此虚拟兼容性的问题没有实际意义.显而易见的第二个问题是get"设置;"即使 C# 支持返回类型协变,属性也不能协变,因为带有 setter 的属性的类型不仅是它的返回类型,也是它的形参类型.您需要对形参类型进行逆变来实现类型安全.如果我们允许使用 setter 的属性返回类型协方差,那么您将拥有:
Now that we have established that the question has been asked using the correct type algebra jargon, a few more thoughts on the actual question. The obvious first problem is that the property has not even been declared as virtual, so questions of virtual compatibilty are moot. The obvious second problem is that a "get; set;" property could not be covariant even if C# did support return type covariance because the type of a property with a setter is not just its return type, it is also its formal parameter type. You need contravariance on formal parameter types to achieve type safety. If we allowed return type covariance on properties with setters then you'd have:
class B
{
public virtual Animal Animal{ get; set;}
}
class D : B
{
public override Tiger Animal { ... }
}
B b = new D();
b.Animal = new Giraffe();
嘿,我们刚刚把一只长颈鹿交给了期待一只老虎的二传手.如果我们支持此功能,我们将不得不将其限制为返回类型(就像我们对泛型接口的赋值兼容性协方差所做的那样.)
and hey, we just passed a Giraffe to a setter that is expecting a Tiger. If we supported this feature we would have to restrict it to return types (as we do with assignment-compatibility covariance on generic interfaces.)
第三个问题是CLR不支持这种方差;如果我们想在语言中支持它(正如我认为托管 C++ 所做的那样),那么我们将不得不采取一些合理的措施来解决 CLR 中的签名匹配限制.
The third problem is that the CLR does not support this kind of variance; if we wanted to support it in the language (as I believe managed C++ does) then we would have to do some reasonably heroic measures to work around signature matching restrictions in the CLR.
你可以通过仔细定义新"来自己做这些英勇的措施.具有隐藏其基类类型的适当返回类型的方法:
You can do those heroic measures yourself by carefully defining "new" methods that have the appropriate return types that shadow their base class types:
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-typed 属性.如果将其强制转换为 B,则会看到 Animal-typed 属性.无论哪种情况,您仍然可以通过受保护成员获得虚拟行为.
Now if you have an instance of D, you see the Tiger-typed property. If you cast it to B then you see the Animal-typed property. In either case, you still get the virtual behaviour via the protected member.
简而言之,抱歉,我们不打算做这个功能.
In short, we have no plans to ever do this feature, sorry.
这篇关于c#使用泛型的协变返回类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!