Saturday, May 16, 2009

Further Generic Variance Thoughts

I was writing a type today that implements both IDictionary<TKey, TValue> and ICollection<TValue>. These interfaces require the implementation of both IEnumerable<TValue> and IEnumerable<KeyValuePair<TKey, TValue>>. In .NET 4, the IEnumerable<T> type will be covariant. This exposes my type to potential ambiguity if it is assigned to a location of type IEnumerable<object> (see the previous post for details). If I were to follow my own advice and forbid the implementation of multiple interfaces which are variants of a single common interface, this type would be illegal. So on further reflection, I have decided to amend my opinion thusly: If there are multiple interface implementations which are variants of a common interface, then there must be implicit implementations of all of the potentially ambiguous members. These public members are then selected by the runtime to satisfy otherwise ambiguous calls. The implicit member implementations need not all be for the same interface. For example, if we have some interface IFoo<out T> with members T Bar(); and T Bat(); and we have some type with implements both IFoo<string> and IFoo<Uri>, it could have the members public string Bar(){} and public Uri Bat(){}. Any call to IFoo<object>.Bar() on an object of this type will execute the IFoo<string> implementation, and IFoo<object>.Bat() will execute the Uri implementation.

I believe that this restriction should be enforced at least at the language level (for all variant-capable languages targeting .NET), if not at the runtime level: all potentially ambiguous members must have public implementations. This resolves the ambiguity in a logical way, allows for more complex type design (which, as in the case of my type today, is desirable), and gives developers the ability to control which implementation will be selected. I think it is a Good Thing.