Covariance and Ilist

Covariance and IList

Update: from .NET 4.5 onwards there is IReadOnlyList<out T> and IReadOnlyCollection<out T> which are both covariant; The latter is basically IEnumerable<out T> plus Count; the former adds T this[int index] {get;}. It should also be noted that IEnumerable<out T> is covariant from .NET 4.0 onwards.

Both List<T> and ReadOnlyCollection<T> (via List<T>.AsReadOnly()) implement both of these.


It can only be covariant if it only has a get indexer, i.e.

public T this[int index] { get; }

But all main collections have {get;set;}, which makes that awkward. I'm not aware of any that would suffice there, but you could wrap it, i.e. write an extension method:

var covariant = list.AsCovariant();

which is a wrapper around an IList<T> that only exposes the IEnumerable<T> and the get indexer...? should be only a few minutes work...

public static class Covariance
{
public static IIndexedEnumerable<T> AsCovariant<T>(this IList<T> tail)
{
return new CovariantList<T>(tail);
}
private class CovariantList<T> : IIndexedEnumerable<T>
{
private readonly IList<T> tail;
public CovariantList(IList<T> tail)
{
this.tail = tail;
}
public T this[int index] { get { return tail[index]; } }
public IEnumerator<T> GetEnumerator() { return tail.GetEnumerator();}
IEnumerator IEnumerable.GetEnumerator() { return tail.GetEnumerator(); }
public int Count { get { return tail.Count; } }
}
}
public interface IIndexedEnumerable<out T> : IEnumerable<T>
{
T this[int index] { get; }
int Count { get; }
}

IList using covariance and contravariance in c#, is this possible?

No, you can't. In your example IList<T> is invariant. IList<T> would require to declare in/out to be covariant/contravariant. It's not possible to do that just by inheriting some interface that is covariant.

Covariance & Contravariance with List vs IEnumerable

I recall seeing a lot of information around this issue on the web previously, so I'm not sure that my answer will really add anything new, but I'll try.

If you're using .NET 4, then notice that the definition of IEnumerable(Of T) is actually IEnumerable(Of Out T). The new Out keyword has been introduced in version 4, which indicates the covariance of this interface. The List(Of T) class, however, is simply defined as List(Of T). The Out keyword is not used here, so that class is not covariant.

I'll provide some examples to try to explain why certain assignments such as the one you're describing can't be done. I see that your question is written in VB, so my apologies for using C#.

Assume that you have the following classes:

abstract class Vehicle
{
public abstract void Travel();
}

class Car : Vehicle
{
public override void Travel()
{
// specific implementation for Car
}
}

class Plane : Vehicle
{
public override void Travel()
{
// specific implementation for Plane
}
}

You can create a list of cars, which can only contain objects derived from Car:

        List<Car> cars = new List<Car>();

You can also create a list of planes, which can only contain objects derived from Plane:

        List<Plane> planes = new List<Plane>();

You can even create a list of Vehicles, which can contain any object derived from Vehicle:

        List<Vehicle> vehicles = new List<Vehicle>();

It's legal to add a car to the list of cars, and it's legal to add a plane to the list of planes. It's also legal to add both a car and a plane to the list of vehicles. Therefore, all of the following lines of code are valid:

        cars.Add(new Car()); // add a car to the list of cars

planes.Add(new Plane()); // add a plane to the list of planes

vehicles.Add(new Plane()); // add a plane to the list of vehicles
vehicles.Add(new Car()); // add a car to the list of vehicles

It's not legal to add a car to the list of planes, nor is it legal to add a plane to the list of cars. The following lines of code won't compile:

        cars.Add(new Plane()); // can't add a plane to the list of cars
planes.Add(new Car()); // can't add a car to the list of planes

Therefore, it's not legal to try to bypass this restriction by assigning the list of cars or the list of planes to the vehicles variable:

        vehicles = cars; // This is not allowed
vehicles.Add(new Plane()); // because then you could do this

Consider what the two lines of code are saying above. It's saying that the vehicles variable is actually a List<Car> object, which should only contain objects derived from Car. However, because List<Vehicle> contains an Add(Vehicle) method, it would theoretically be possible to add a Plane object to the List<Car> collection, which is definitely not correct.

However, it's perfectly valid to assign a list of cars or a list of planes to an IEnumerable<Vehicle> variable.

        IEnumerable<Vehicle> vehicles = cars;

foreach (Vehicle vehicle in vehicles)
{
vehicle.Travel();
}

The quick explanation here is that the IEnumerable interface doesn't allow you to manipulate the collection. It is essentially a read-only interface. The T objects (Vehicles in this case) are only exposed as a return value on the IEnumerable interface's Current property. There are no methods that take Vehicle objects as input parameters, therefore there is no danger of the collection being modified in an illegal way.

Side Note: I've always thought that it would make sense for the IList<T> interface to be a composite of an IReadableList<out T> interface and an IWritableList<in T> interface.

How to Convert IList SomeObject to IList ISomeInterface where SomeObject implements ISomeInterface using covariance in C# 4.0

why not simply use

IList<Items> GetItems;
IList<IItems> items = GetItems().Cast<IItems>().ToList();

IList T contravariance and casting issue

An IList<IDataRecord> should let you add any IDataRecord to it; your list only lets you add VallDataData.

If you only need to read from the list, try using IReadOnlyList<IDataRecord> instead, as it is actually covariant.

Using generic contravariant with IList and IEnumerable

The question why it's not allowed for IList<T> has been answered in the comments and linked questions already: IList<T> is invariant in T and so a contra-variant T cannot be used here whatsoever.

What puzzled me at first is the fact that Method(IEnumerable<T>) is allowed here. The strange thing is that variance is "turned around" when you use the T as a type argument for another generic type.

Imagine this.

public interface ITestInterface<in T>
{
void Method(IEnumerable<T> e);
IEnumerable<T> GetMethod(); // illegal
}
public class Animal {}
public class Lion : Animal [}
public class Gnu : Animal {}

ITestInterface<Animal> animals;
ITestInterface<Lion> lions;
ITestInterface<Gnu> gnus;

Now the contra-variance of ITestInterface<in T> in T tells us that you can do

lions = animals;

And when you call lions.Method(e), you can only provide an IEnumerable<Lion>. So the code of Method can only enumerate Lions, which are all Animals as animals.Method() expects. Everything is fine.

On the other hand, the IEnumerable<T> GetMethod() is illegal, because:

gnus = animals;

is legal, and now gnu.GetMethod() would return an IEnumerable<Animal> where you'd expect an IEnumerable<Gnu>. And when you iterated, suprising animals could wait in that sequence.

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.

Question about C# covariance

Simply put, IList<T> is not covariant, whereas IEnumerable<T> is. Here's why...

Suppose IList<T> was covariant. The code below is clearly not type-safe... but where would you want the error to be?

IList<Apple> apples = new List<Apple>();
IList<Fruit> fruitBasket = apples;
fruitBasket.Add(new Banana()); // Aargh! Added a Banana to a bunch of Apples!
Apple apple = apples[0]; // This should be okay, but wouldn't be

For lots of detail on variance, see Eric Lippert's blog post series on it, or watch the video of my talk about variance from NDC.

Basically, variance is only ever allowed where it's guaranteed to be safe (and in a representation-preserving way, which is why you can't convert IEnumerable<int> into IEnumerable<object> - the boxing conversion doesn't preserve representation).



Related Topics



Leave a reply



Submit