Reputation: 39826
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.
Upvotes: 2
Views: 628
Reputation: 107950
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 :
Upvotes: 6
Reputation: 28425
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
Upvotes: 0
Reputation: 116664
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.
Upvotes: 1