Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using base class as generic for IEnumerable<T>

I have a good understanding of OOP in general, inheritance and polymorphism, interfaces, etc. I encountered a strange situation and I don't understand why it does not work at all...

EDIT : Ok, I found out that covariance (or contravariance?) may solve this problem, but crucially

we're still using .NET 2.0

How can I solve this without moving to C# 4.0 ?

Here is the situation. Given these two classes :

public class CustomCollectionType<T> : IEnumerable<T>
{
    /* Implementation here, not really important */
}

public class Entity : EntityBase
{
    /* Implentation here, not important */
}

The compiler complains when I try to have this generic method

public void LoopThrough(IEnumerable<EntityBase> entityList)
{
    foreach(EntityBase entity in entityList) 
    {
        DoSomething(entity);  
    }
}

And try to use it this way :

CustomCollectionType<Entity> entityList;
/* Add items to list */

LoopThrough(entityList);

Error says I cannot convert from CustomCollectionType<Entity> to IEnumerable<EntityBase>.

However, I can do this :

public void Foo(EntityBase entity)
{
    entity.DoSomething();
}

Foo(new Entity());

And this :

public void Bar(IEnumerable<Entity> entityList)
{ ... }

CustomCollectionType<Entity> entityList;

Bar(entityList);

Why can't I create my method with the highest classes in the hierarchy? The types are obviously compatible... Am I missing something ?

EDIT : I want to solve this problem without altering the existing classes in any way, so creating a new method in any of the classes, or implementing an additional interface is out of the question.

like image 213
marco-fiset Avatar asked Dec 02 '22 01:12

marco-fiset


1 Answers

Let's consider your first case. You have:

class Bowl<T> : IEnumerable<T> {}
class Apple : Fruit {}
...
void LoopThrough(IEnumerable<Fruit> fruits) ...

and you call

Bowl<Apple> apples = whatever;
LoopThrough(apples);

This fails in C# 3.0; it succeeds in C# 4.0 because IEnumerable<T> is now covariant in T; a sequence of apples can be used as a sequence of fruits.

To make it work in C# 3.0 you can use the Cast sequence operator.

Bowl<Apple> apples = whatever;
LoopThrough(apples.Cast<Fruit>());

To make it work in C# 2.0, implement the Cast sequence operator yourself. It is only a couple lines of code.

Note that in C# 4.0 it will still not be legal to say:

Bowl<Fruit> fruits = new Bowl<Apples>();

because of course you can say:

fruits.Add(new Orange());

and you just put an orange into a bowl that can only contain apples.

like image 100
Eric Lippert Avatar answered Dec 04 '22 12:12

Eric Lippert