Does C# Support Return Type Covariance

Does C# support return type covariance?


UPDATE: This answer was written in 2011. After two decades of people proposing return type covariance for C# they have been implemented. See Covariant Returns in https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/.


It sounds like what you want is return type covariance. C# does not support return type covariance.

Return type covariance is where you override a base class method that returns a less-specific type with one that returns a more specific type:

abstract class Enclosure
{
public abstract Animal Contents();
}
class Aquarium : Enclosure
{
public override Fish Contents() { ... }
}

This is safe because consumers of Contents via Enclosure expect an Animal, and Aquarium promises to not only fulfill that requirement, but moreover, to make a more strict promise: that the animal is always a fish.

This kind of covariance is not supported in C#, and is unlikely to ever be supported. It is not supported by the CLR. (It is supported by C++, and by the C++/CLI implementation on the CLR; it does so by generating magical helper methods of the sort I suggest below.)

(Some languages support formal parameter type contravariance as well -- that you can override a method that takes a Fish with a method that takes an Animal. Again, the contract is fulfilled; the base class requires that any Fish be handled, and the derived class promises to not only handle fish, but any animal. Similarly, C# and the CLR do not support formal parameter type contravariance.)

The way you can work around this limitation is to do something like:

abstract class Enclosure
{
protected abstract Animal GetContents();
public Animal Contents() { return this.GetContents(); }
}
class Aquarium : Enclosure
{
protected override Animal GetContents() { return this.Contents(); }
public new Fish Contents() { ... }
}

Now you get both the benefits of overriding a virtual method, and getting stronger typing when using something of compile-time type Aquarium.

C# return type covariance and Liskov substitution principle

C# can still apply the Liskov substitution principle.

Consider:

public class Base1
{
}

public class Derived1 : Base1
{
}

public class Base2
{
public virtual Base1 Method()
{
return new Base1();
}
}

public class Derived2 : Base2
{
public override Base1 Method()
{
return new Derived1();
}
}

If C# supported covariant return types, then the override for Method() in Base2 could be declared thusly:

public class Derived2 : Base2
{
public override Derived1 Method()
{
return new Derived1();
}
}

C# does not allow this, and you must declare the return type the same as it is in the base class, namely Base1.

However, doing so does not make it violate the Liskov substitution principle.

Consider this:

Base2 test = new Base2();
Base1 item = test.Method();

Compared to:

Base2 test = new Derived2();
Base1 item = test.Method();

We are completely able to replace new Base2() with new Derived2() with no issues. This complies with the Liskov substitution principle.

c# 9.0 covariant return types and interfaces

Whilst covariant return types in interfaces are not supported as of C# 9, there is a simple workaround:

    interface A {
object Method1();
}

class B : A {
public string Method1() => throw new NotImplementedException();
object A.Method1() => Method1();
}

c# covariant return types utilizing generics

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.

Workaround for lack of return type covariance when overriding virtual methods

You can do some pretty zany stuff with generics.

public class Alpha<T> where T: Alpha<T> {
public virtual T DoSomething() {
throw new NotImplementedException();
}
}
public class Beta : Alpha<Beta> {
public override Beta DoSomething() {
throw new NotImplementedException();
}
}

C# Covariance on subclass return types

UPDATE: This answer was written in 2011. 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.


First off, return type contravariance doesn't make any sense; I think you are talking about return type covariance.

See this question for details:

Does C# support return type covariance?

You want to know why the feature is not implemented. phoog is correct; the feature is not implemented because no one here ever implemented it. A necessary but insufficient requirement is that the feature's benefits exceed its costs.

The costs are considerable. The feature is not supported natively by the runtime, it works directly against our goal to make C# versionable because it introduces yet another form of the brittle base class problem, Anders doesn't think it is an interesting or useful feature, and if you really want it, you can make it work by writing little helper methods. (Which is exactly what the CIL version of C++ does.)

The benefits are small.

High cost, small benefit features with an easy workaround get triaged away very quickly. We have far higher priorities.

c# object interface return type covariance

You can implement interface explicitly. https://msdn.microsoft.com/en-us/library/ms173157.aspx

The same pattern is used on non generic version of IEnumerable and generic IEnumerable<T>

You can do the same and have generic interface too.

public interface Field
{
object Value { get; }
}

public interface Field<T> : Field
{
new T Value { get; }
}

public class MyField<T> : Field<T>
{
public T Value { get; } // generic

object Field.Value => Value; // non generic
}

Now if you have Field<T> on your hand you can use T happily. if you have Field you get object form of value T

why covariant type parameters are used only for the return types of the members?

Why do covariant type parameters like IEnumerable<out T> have type T used only for the return type?

First off: T does not have to be used only for the return type. For example:

interface I1<out T>{ T M1(); }
interface I2<out T>{ void M2(Action<I1<T>> a); }

In I1<T>, T is used only in return type positions. But in I2, T is used in an input a, and an I1<T> is the input of the Action, so in a sense it is being used in two input positions here.

But let's consider just a simpler case. Why can we make I1 covariant in T but not contravariant in T?

The reason is because covariance is safe and contravariance is not. We can see that covariance is safe:

class Animal {}
class Mammal : Animal {}
class Tiger : Mammal {}
class Giraffe : Mammal {}
class C : I1<Mammal> {
public Mammal M1() { return new Tiger(); }
}
I1<Mammal> i1m = new C(); // Legal
I1<Animal> i1a = i1m; // Legal
Animal a = i1a.M1(); // Returns a tiger; assigned to animal, good!

No matter what C.M1 returns, it is always a Mammal and therefore always an Animal.

But this cannot be legal:

I1<Giraffe> i1g = i1m; // Not legal
Giraffe g = i1g.M1(); // Returns a tiger; assigned to giraffe, bad!

The first line has to be illegal so that the second line never executes.

Now you should have enough information to figure out why contravariance works the way it does. Remember, you can always come back to a simple example and ask yourself "if this was legal, what mistakes could I make later?" The type system is protecting you from making those mistakes!

Exercise: Do this same analysis of I2<T>. Do you see why it is legal to use T in two input positions even though it is out. (Hint: Action is contravariant, so it reverses the direction of assignment compatibility. What happens if you reverse directions twice?)

subtype and covariant return type

Unlike java, covariant return types are not supported in C#. I believe this is due to the implementation of C# properties, if covariant return types were allowed, the following would be possible:

class TypeX { }

class TypeY : TypeX { }

class Base
{
public virtual TypeX Prop { get; set; }
}

class Derived : Base
{
public override TypeY Prop { get; set; }
}

Derived derived = new Derived();
derived.Prop = new TypeY(); // Valid

Base @base = derived;
@base.Prop = new TypeX(); // Invalid - As this would be using the derived property which should be of TypeY

See Eric Lippert's answer for more information.



Related Topics



Leave a reply



Submit