.NET Generics and Type Inference Landmine
May 21, 2008 programming 4 CommentsLet’s say that you have a class that takes a generic type parameter:
public class Foo<T> {
public void accept(T someObject) { }
public void doSomething() { }
}
It’s difficult to doSomething() to a collection of Foo<T>s when you don’t know what T is and/or there’s multiple types used for T. To work around this, you can create a typeless interface:
public interface FooTypeless {
void doSomething();
}
The declaration of Foo<T> now becomes:
public class Foo<T> : FooTypeless
Now you can have this method:
public void doSomethingOnAllFoos(
IEnumerable<FooTypeless> foos) {
foreach( FooTypeless foo in foos ) {
foo.doSomething();
}
}
Also, say that you have the following extension method:
public static IEnumerable<T> toEnumerable<T>(this <T> obj ) {
// Any IEnumerable implementation should work here;
// but we'll get to that in a bit
return new T[] { obj };
}
Type inference would let you write this:
FooaFoo = new Foo (); doSomethingOnAllFoos( aFoo.toEnumerable() );
However, this code does not work; you get a compile error:
cannot convert from ‘System.Collections.Generic.IEnumerable<Foo<string>>’ to ‘System.Collections.Generic.IEnumerable<FooTypeless>’
This occurs even though Foo<string> inherits from FooTypeless. Even though the generic type parameters are compatible, the generic-enabled reference isn’t, at least according to the compiler. (We humans could probably see that IEnumerable is an interface that could safely be converted, but the existing compiler cannot).
Now, you can do this:
IEnumerable<FooTypeless> someFoos
= new Foo<String>[] { foo };
…but not this:
IEnumerable<Foo<String>> someStringFoos
= new Foo<String>[] { foo };
IEnumerable<FooTypeless> someFoos
= someStringFoos;
You may be tempted to try casting to work around this. For example, this works:
IEnumerable<Foo<String>> someStringFoos
= new Foo<String>[] { foo };
IEnumerable<FooTypeless> someFoos
= (IEnumerable<FooTypeless>) someStringFoos;
However, this only works if someStringFoos is assigned an array. The following does not work:
IEnumerable<Foo<String>> someStringFoos
= new List<Foo<String>> { foo };
IEnumerable<FooTypeless> someFoos
= (IEnumerable<FooTypeless>) someStringFoos;
If you try this, you’ll get an InvalidCastException. Furthermore, this generates the compiler error:
IEnumerable<FooTypeless> someFoos
= new List<Foo<String>> { foo };
Currently I have no idea why the array works but the List doesn’t; it doesn’t make a lot of sense to me.
I originally discovered this by using type inference with my toEnumerable() extension method. An easy way around these problems is to avoid the type inference completely and explicitly specify the type of IEnumerable you want:
Foo<String> aFoo = new Foo<String>(); doSomethingOnAllFoos( aFoo.toEnumerable<FooTypeless>() );
This works fine, even if toEnumerable() uses a non-array type (such as List) as its IEnumerable implementation. Execution-wise, nothing has changed, but the generic type used in the call can make or break the code.
Stuff like this starts to make Java’s type erasure look not so ugly in comparison. ![]()
