简体   繁体   中英

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.

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.

Yes, .NET can be kind of annoying that way as it can't cast all of your generic parameters in one shot. Instead, perhaps try a generic approach like this to alleviate the problem.

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

The types are compatible but sort of uncompatible, the major reason here is that you are using the base type in parameter as IEnumerable and not the actual type although Entity's base is entitybase because the rules for type parameters and constraints have several implications for generic class behavior, especially regarding inheritance and member accessibility

Generic classes are invariant. In other words, if an input parameter specifies a List<BaseClass>, you will get a compile-time error if you try to provide a List<DerivedClass> .

And thats why you get that error where as in your last eg the T is same.

However it would have worked absolutely fine had you used interfaces because all interfaces are compatible

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

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

and than your method will work fine

CustomCollectionType<Entity> entityList;      LoopThrough(entityList);  

because entitylist has a type of IEntityBase

The other thing you can try is typeof(to get type) or using a cast and it should work

I might be missing something, but if your intent of CustomCollectionType is supposed to be of an Entity base yet allowed to use the IEnumerable, shouldn't you have IT as a base of Entity base first? such as...

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

Then your LoopThrough SHOULD work as the custom collection type is derived FROM the EntityBase and have whatever expected methods, properties, etc available... or worst case, you would type-cast it when calling the function such as

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM