views:

167

answers:

3

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.

+6  A: 

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 :

Andreas Grech
So if I understand correctly: In my example, I couldn't pass an instance of List<Stuff1> or List<Stuff2> without the type constraint, and so would have to pass an instance of List<StuffBase> whereas using the type constraint, I could?
BenAlabaster
i.e. AllDoSomething<T>(List<T> items) where T : StuffBase would allow me to pass in instances of List<Stuff1> or List<Stuff2>
BenAlabaster
yes, because that way you are 'bypassing' covariance of formal parameters through a simple generic polymorphic constraint
Andreas Grech
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
+1  A: 

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
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
The thing remember is that a variable of type `List<B>` cannot have an object of type `List<D>` 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<D>`, you should not be able to silently convert it to a `List<B>`, because that would allow `B` objects to be added to it (when it's actually a `List<D>`).
Daniel Earwicker
A: 

Hello,

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