2

I've got a base class:

public abstract class StuffBase
{
    public abstract void DoSomething();
}

And two derived classes

public class Stuff1 : StuffBase
{
    public void DoSomething()
    {
        Console.WriteLine("Stuff 1 did something cool!");
    }
    public Stuff1()
    {
        Console.WriteLine("New stuff 1 reporting for duty!");
    }
}

public class Stuff2 : StuffBase
{
    public void DoSomething()
    {
        Console.WriteLine("Stuff 2 did something cool!");
    }
    public Stuff1()
    {
        Console.WriteLine("New stuff 2 reporting for duty!");
    }
}

Okay, now say I've got a list of items:

var items = new List<StuffBase>();
items.Add(new Stuff1());
items.Add(new Stuff2());

and I want them all to call their DoSomething() method. I could expect to just iterate the list and call their DoSomething() method, so let's say I've got a method to do that called AllDoSomething() that just iterates over the list and does the job:

public static void AllDoSomething(List<StuffBase> items)
{
    items.ForEach(i => i.DoSomething());
}

What is the practical difference of the following method?

public static void AllDoSomething<T>(List<T> items) where T: StuffBase
{
    items.ForEach(i => i.DoSomething());
}

Both methods appear in real terms, although being syntactically different, to be doing the same thing.

Are they just different ways of doing the same thing? I understand generics and type constraints but can't see why I would use one way over the other in this instance.

BenAlabaster
  • 39,070
  • 21
  • 110
  • 151

3 Answers3

6

This is because as of yet, C# does not support Covariance.

More formally, in C# v2.0 if T is a subtype of U, then T[] is a subtype of U[], but G is not a subtype of G (where G is any generic type). In type-theory terminology, we describe this behavior by saying that C# array types are “covariant” and generic types are “invariant”.

Reference: http://blogs.msdn.com/rmbyers/archive/2005/02/16/375079.aspx

If you have the following method :

public static void AllDoSomething(List<StuffBase> items)
{
    items.ForEach(i => i.DoSomething());
}

var items = new List<Stuff2>();
x.AllDoSomething(items); //Does not compile

Where as if you use the generic type constraint, it will.

For more information about Covariance and Contravariance], check out Eric Lippert's series of posts.


Other posts worth reading :

Community
  • 1
  • 1
Andreas Grech
  • 105,982
  • 98
  • 297
  • 360
  • So if I understand correctly: In my example, I couldn't pass an instance of List or List without the type constraint, and so would have to pass an instance of List whereas using the type constraint, I could? – BenAlabaster Aug 02 '09 at 20:24
  • i.e. AllDoSomething(List items) where T : StuffBase would allow me to pass in instances of List or List – BenAlabaster Aug 02 '09 at 20:25
  • yes, because that way you are 'bypassing' covariance of formal parameters through a simple generic polymorphic constraint – Andreas Grech Aug 02 '09 at 21:48
  • 1
    Thanks, that makes perfect sense. I guess it wasn't that I didn't udnerstand covariance per se, I was just missing the point. I get it now. – BenAlabaster Aug 03 '09 at 15:53
1

Suppose you had a list:

List<Stuff1> l = // get from somewhere

Now try:

AllDoSomething(l);

With the generic version, it will be allowed. With the non-generic, it won't. That's the essential difference. A list of Stuff1 is not a list of StuffBase. But in the generic case, you don't require it to be exactly a list of StuffBase, so it's more flexible.

You could work around that by first copying your list of Stuff1 into a list of StuffBase, to make it compatible with the non-generic version. But then suppose you had a method:

List<T> TransformList<T>(List<T> input) where T : StuffBase
{
    List<T> output = new List<T>();

    foreach (T item in input)
    {
        // examine item and decide whether to discard it,
        // make new items, whatever
    }

    return output;
}

Without generics, you could accept a list of StuffBase, but you would then have to return a list of StuffBase. The caller would have to use casts if they knew that the items were really of a derived type. So generics allow you to preserve the actual type of an argument and channel it through the method to the return type.

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
  • So the essential difference is that my list couldn't be defined as a list of my derived type - while a list of my base class could contain my derived types? – BenAlabaster Aug 02 '09 at 20:21
  • The thing remember is that a variable of type `List` cannot have an object of type `List` assigned to it, even though a variable of type `B` can accept a `D`. It's because a class like List can have a method like `Add`. If a variable is a `List`, you should not be able to silently convert it to a `List`, because that would allow `B` objects to be added to it (when it's actually a `List`). – Daniel Earwicker Aug 02 '09 at 21:09
0

In the example you provided there is no difference but try the following:

List<Stuff1> items = new List<Stuff1>();
items.Add(new Stuff1());
AllDoSomething(items);
AllDoSomething<StuffBase>(items);

The first call works well but the second one does not compile because of generic covariance

Victor Hurdugaci
  • 28,177
  • 5
  • 87
  • 103