BenAlabaster
BenAlabaster

Reputation: 39826

When should or shouldn't I be using generic type constraints?

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

Answers (3)

Andreas Grech
Andreas Grech

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

Victor Hurdugaci
Victor Hurdugaci

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

Daniel Earwicker
Daniel Earwicker

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

Related Questions