views:

87

answers:

2

Suppose I have a small inheritance hierarchy of Animals:

public interface IAnimal {
    string Speak();
}

public class Animal : IAnimal {
    public Animal() {}
    public string Speak() {
        return "[Animal] Growl!";
    }
}

public class Ape : IAnimal {
    public string Speak() {
        return "[Ape] Rawrrrrrrr!";
    }
}

public class Bat : IAnimal {
    public string Speak() {
        return "[Bat] Screeeeeee!";
    }
}

Next, here's an interface offering a way to turn strings into IAnimals.

public interface ITransmogrifier<T> where T : IAnimal {
    T Transmogrify(string s);
}

And finally, here's one strategy for doing that:

public class Transmogrifier<T> : ITransmogrifier<T> where T : IAnimal, new() {
    public T Transmogrify(string s) {
        T t = default(T);
        if (typeof(T).Name == s)
            t = new T();
        return t;
    }
}

Now, the question. Is it possible to replace the sections marked [1], [2], and [3] such that this program will compile and run correctly? If you can't do it without touching parts other than [1], [2], and [3], can you still get an IAnimal out of each instance of a Transmogrifier in a collection containing arbitrary implementations of an IAnimal? Can you even form such a collection to begin with?

    static void Main(string[] args) {
        var t = new Transmogrifier<Ape>();
        Ape a = t.Transmogrify("Ape");
        Console.WriteLine(a.Speak());  // Works!

        // But can we make an arbitrary collection of such animals?
        var list = new List<Transmogrifier< [1] >>() {
            // [2]
        };

        // And how about we call Transmogrify() on each one?
        foreach (/* [3] */ transmogrifier in list) {
            IAnimal ia = transmogrifier.Transmogrify("Bat");
        }
    }
}
+3  A: 

You can't do this, since there is no type relationship between Transmorgifier<Ape> and Transmorgifier<Bat> so you can't put them in the same generic list (except List<object>). The obvious solution is just to make ITransmorgifier non-generic:

public interface ITransmogrifier
{
    IAnimal Transmogrify(string s);
}

Then you could have

var list = new List<ITransmogrifier>() {
    new Transmorgifier<Ape>(), new Transmorgifier<Bat>() ...
};
Lee
I suspected as much. I was hoping there'd be some trickery I could do with `.Cast<T>`, but I guess not.
John Feminella
+4  A: 

Lee is correct.

As you imply by your question, you can do this in C# 4 by marking ITransmogrifier as covariant ("out") in T. You can then make a List<ITransmogrifier<IAnimal>> and put a Transmogrifier<Bat> into that list.

Eric Lippert
@Eric: Indeedy, I'm using `in`/`out` with reckless and gleeful abandon in C# 4. Immensely useful.
John Feminella