5

Given the following LinqPad example:

void Main()
{   
    List<DbItem> dbItems = new List<DbItem>
    {
        new DbItem{Id = 4, SomeValue = "Value For 4", OtherValue = 1},
        new DbItem{Id = 19, SomeValue = "Value For 19", OtherValue = 2}
    };

    ListMethod(dbItems.Cast<IDbItem>().ToList());   <-+
    EnumerableMethod(dbItems);                      <-+
}                                                     |  
                                                      |
//    These are the methods are I asking about -------+

public void ListMethod(List<IDbItem> dbItems)
{
    Console.WriteLine(dbItems);
}
public void EnumerableMethod(IEnumerable<IDbItem> dbItems)
{
    Console.WriteLine(dbItems);
}
public class DbItem : IDbItem
{
    public int Id {get; set;}
    public string SomeValue {get; set;}
    public int OtherValue {get; set;}
}   
public interface IDbItem 
{
    int Id {get; set;}
    string SomeValue {get; set;}
}

Why can EnumerableMethod take the list directly while ListMethod needs a cast first?

I get that an cast is happening from DbItem to IDbItem, but what is different about the IEnumerable that allows it to make the cast without an express request?

I imagine the answer involves the word Covariance, but I don't know enough about that to just figure it out myself.

Vaccano
  • 78,325
  • 149
  • 468
  • 850

3 Answers3

4

Check out this post about covariance. He gives an explanation and a way to make the code work without the cast. Lists are not covariant, but you can just change the method like so:

public void ListMethod<T>(List<T> dbItems) where T : IDbItem
{
    Console.WriteLine(dbItems);
}

Covariance is the ability to assign a more derived generic type to a base generic type. The reason List is not covariant is because you could do this:

class Animal {}
class Dog : Animal {}
class Cat : Animal {}

void someFunction()
{
    List<Animal> dogs = new List<Dog>();
    dogs.Add(new Cat());
}

dogs is really a list of dogs, not of animals. Therefore adding a Cat to it would be a problem, specifically causing a compiler error.

ShadowCat7
  • 804
  • 8
  • 16
  • I get that a dog is really a list of dogs and not animals. But then why does it work for IEnumerable? – Vaccano Sep 09 '13 at 17:01
  • `IEnumerable` does not have an `Add` method, so there's no fear of adding a `Cat` to `dogs`. Covariance is something that must be explicitly specified, and the guys who made `List` decided that it shouldn't be covariant for the reason above. The guys who made `IEnumerable` decided that it should be covariant. – ShadowCat7 Sep 09 '13 at 18:20
3

I imagine the answer involves the word Covariance

Yes you are correct, The type parameter for IEnumerable<T> is covariant. so you can use either the type you specified or any type that is more derived

   public interface IEnumerable<out T> : IEnumerable

Where as List<T> type parameter is not covariant

public class List<T> : IList<T>, ICollection<T>, 
    IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, 
    IEnumerable
sa_ddam213
  • 42,848
  • 7
  • 101
  • 110
0

Because you can't cast a collection of x's to a collection of y's, even though x derives from y and therefore every x is a y. See this SO question.

This is why the covariance and contravariance concepts were added to the language.

Unfortunately List<T> does not support these features.

Community
  • 1
  • 1
Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
  • .NET 4.0 / C# 4.0 only applies to generic interfaces and generic delegate declarations; it does not apply to concrete types (so it's not a support feature of List, but of all concrete types). – Erik Philips Sep 06 '13 at 00:02