.NET Generics and Type Inference Landmine

programming 4 Comments

Let’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:

Foo aFoo = 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. :-P

Stupid .NET Tricks #13

programming No Comments

In Java, making a field “final” means that you can only assign a value to it once. It’s an important part of making a class immutable. It helps to prevent bugs too: make a field final and you’ll get a compiler error if you leave it unassigned or try to reassign it anywhere.

.NET has a similar concept with the “readonly” keyword for fields. However, there’s one important difference compared to Java: a “readonly” field can only be assigned in the class’s constructor, but it can be assigned multiple times within that constructor. The only restriction it places is that the field can’t be reassigned outside of the constructor. You don’t even get a compiler error for not assigning it at all; you only get a compiler warning (which can be turned off).

This has encouraged bugs at least twice in my code: I assigned a value to a field twice within the constructor and then got unexpected results due to the incorrect object being used. One of these was caused by a conflict resolution from a Subversion Merge (and thus it was less-than-obvious that it had been introduced).

New Perspective

usa, world No Comments

Alternate headline: “New Spirit of Chinese-American Cooperation comes to Cuba”