Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
194 views
in Technique[技术] by (71.8m points)

c# covariant return types utilizing generics

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> {}

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?

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


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.

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 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?

The answer in C# is "no". C# does not support this kind of covariance.

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.)

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(); } }
}

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...