C# Covariant Return Types Utilizing Generics

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.

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# covariant/contravariant types utilizing generics with Dictionary

You can apply generic constraints in BaseEntityMap<TSource, TTarget> class to your BaseSource and BaseTarget classes (or apply constraints in IEntityMapper<in TSource, out TTarget> interface and get rid of base abstract class)

abstract class BaseEntityMap<TSource, TTarget> : IEntityMapper<TSource, TTarget>
where TSource : BaseSource
where TTarget : BaseTarget
{
public abstract TTarget Map(TSource entity);
}

Then declare mappers dictionary like below, using Func<BaseSource, BaseTarget> delegate. It should be fine, since you've constrained your BaseEntityMap<TSource, TTarget> and Func<in T, out TResult> declaration supports contravariance for the input parameter and covariance for the result

IDictionary<Type, Func<BaseSource, BaseTarget>> MappersMap = new Dictionary<Type, Func<BaseSource, BaseTarget>>();

public Workflow()
{
var orderMapper = new OrderEntityMapper();
MappersMap.Add(typeof(SourceEntityA), source => orderMapper.Map((SourceEntityA)source));
var goodsMapper = new GoodsEntityMapper();
MappersMap.Add(typeof(SourceEntityC), source => goodsMapper.Map((SourceEntityC)source));
//....more mappers
}

The usage example

foreach (var entity in entities)
{
var target = mapper(entity);
//.... code continues
}

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.

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.

A generic method can use contravariant/covariant types?

This question is quite confusing. Let me see if I can clarify it.

When I try to implement IGreatInterface the compiler flags an error for aMethodBeta() because I've made that method using a subtype of IAnInterface I want to implement that method like this: Object aMethodBeta(AnInterestingClass parameter).

That's not legal. Simplifying somewhat:

class Food {}
class Fruit : Food {}
class Meat : Food {}
interface IEater
{
void Eat(Food food);
}
class Vegetarian : IEater
{
public void Eat(Fruit fruit);
}

Class Vegetarian does not fulfill the contract of IEater. You should be able to pass any Food to Eat, but a Vegetarian only accepts Fruit. C# does not support virtual method formal parameter covariance because that is not typesafe.

Now, you might then say, how about this:

interface IFruitEater
{
void Eat(Fruit fruit);
}
class Omnivore : IFruitEater
{
public void Eat(Food food);
}

Now we have got type safety; Omnivore can be used as an IFruitEater because an Omnivore can eat fruit, as well as any other food.

Unfortunately, C# does not support virtual method formal parameter type contravariance even though doing so is in theory typesafe. Few languages do support this.

Similarly, C# does not support virtual method return type variance either.

I'm not sure if that actually answered your question or not. Can you clarify the question?

UPDATE:

What about:

interface IEater
{
void Eat<T>(T t) where T : Food;
}
class Vegetarian : IEater
{
// I only want to eat fruit!
public void Eat<Fruit>(Fruit food) { }
}

Nope, that's not legal either. The contract of IEater is that you will provide a method Eat<T> that can take any T that is a Food. You cannot partially implement the contract, any more than you could do this:

interface IAdder
{
int Add(int x, int y);
}
class Adder : IAdder
{
// I only know how to add two!
public int Add(2, int y){ ... }
}

However, you can do this:

interface IEater<T> where T : Food
{
void Eat(T t);
}
class Vegetarian : IEater<Fruit>
{
public void Eat(Fruit fruit) { }
}

That is perfectly legal. However, you cannot do:

interface IEater<T> where T : Food
{
void Eat(T t);
}
class Omnivore : IEater<Fruit>
{
public void Eat(Food food) { }
}

Because again, C# does not support virtual method formal parameter contravariance or covariance.

Note that C# does support parametric polymorphism covariance when doing so is known to be typesafe. For example, this is legal:

IEnumerable<Fruit> fruit = whatever;
IEnumerable<Food> food = fruit;

A sequence of fruit may be used as a sequence of food. Or,

IComparable<Fruit> fruitComparer = whatever;
IComparable<Apples> appleComparer = fruitComparer;

If you have something that can compare any two fruits then it can compare any two apples.

However, this kind of covariance and contravariance is only legal when all of the following are true: (1) the variance is provably typesafe, (2) the author of the type added variance annotations indicating the desired co- and contra-variances, (3) the varying type arguments involved are all reference types, (4) the generic type is either a delegate or an interface.

Covariant generic parameter

Can someone explain the usage of the out T part with an example?

Sure. IEnumerable<T> is covariant. That means you can do this:

static void FeedAll(IEnumerable<Animal> animals) 
{
foreach(Animal animal in animals) animal.Feed();
}

...

IEnumerable<Giraffe> giraffes = GetABunchOfGiraffes();
FeedAll(giraffes);

"Covariant" means that the assignment compatibility relationship of the type argument is preserved in the generic type. Giraffe is assignment compatible with Animal, and therefore that relationship is preserved in the constructed types: IEnumerable<Giraffe> is assignment compatible with IEnumerable<Animal>.

Why is applicable only for interfaces and delegates and not for classes?

The problem with classes is that classes tend to have mutable fields. Let's take an example. Suppose we allowed this:

class C<out T>
{
private T t;

OK, now think this question through carefully before you go on. Can C<T> have any method outside of the constructor that sets the field t to something other than its default?

Because it must be typesafe, C<T> can now have no methods that take a T as an argument; T can only be returned. So who sets t, and where do they get the value they set it from?

Covariant class types really only work if the class is immutable. And we don't have a good way to make immutable classes in C#.

I wish we did, but we have to live with the CLR type system that we were given. I hope in the future we can have better support for both immutable classes, and for covariant classes.

If this feature interests you, consider reading my long series on how we designed and implemented the feature. Start from the bottom:

https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/

Error CS1503 when casting covariant generic interface types

Yes, this is expected behavior for covariance with generics, and it's to do with the representation of the return value from the Func.

When T is constrained via a class, the runtime knows that the value returned by the delegate will always be a reference - so it just needs to allocate enough space for a reference, regardless of what actual func type it was.

When T is constrained via an interface, but without the additional "class" constraint, you could pass in a delegate that returns a value type value, not a reference. That breaks all kinds of things that are assumed by covariance.

However, you can still use an interface - so long as you constrain T with the class constraint as well. Change your method signature like this, and it compiles:

public static void Register<T>(Func<string, T> constructor) where T : class, ITest

For more information about representation (and identity), see Eric Lippert's blog post on the topic and indeed his whole series on generic variance.

Covariance with C# Generics

The fact that AMQuestion implements the IQuestion interface does not translate into List<AMQuestion> deriving from List<IQuestion>.

Because this cast is illegal, your as operator returns null.

You must cast each item individually as such:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();

Regarding your comment, consider the following code, with the usual cliché animal examples:

//Lizard and Donkey inherit from Animal
List<Lizard> lizards = new List<Lizard> { new Lizard() };
List<Donkey> donkeys = new List<Donkey> { new Donkey() };

List<Animal> animals = lizards as List<Animal>; //let's pretend this doesn't return null
animals.Add(new Donkey()); //Reality unravels!

if we were allowed to cast List<Lizard> to a List<Animal>, then we could theoretically add a new Donkey to that list, which would break inheritance.



Related Topics



Leave a reply



Submit