Ambiguous call between two C# extension generic methods one where T:class and other where T:struct

EDIT: I’ve now blogged about this in more detail.


My original (and I now believe incorrect) thought: generic constraints aren’t taken into account during the overload resolution and type inference phases – they’re only used to validate the result of the overload resolution.

EDIT: Okay, after a lot of going round on this, I think I’m there. Basically my first thought was almost correct.

Generic type constraints only act to remove methods from a candidate set in a very limited set of circumstances… in particular, only when the type of a parameter itself is generic; not just a type parameter, but a generic type which uses a generic type parameter. At that point, it’s the constraints on the type parameters of the generic type which are validated, not the constraints on the type parameters of the generic method you’re calling.

For example:

// Constraint won't be considered when building the candidate set
void Foo<T>(T value) where T : struct

// The constraint *we express* won't be considered when building the candidate
// set, but then constraint on Nullable<T> will
void Foo<T>(Nullable<T> value) where T : struct

So if you try to call Foo<object>(null) the above method won’t be part of the candidate set, because Nullable<object> value fails to satisfy the constraints of Nullable<T>. If there are any other applicable methods, the call could still succeed.

Now in the case above, the constraints are exactly the same… but they needn’t be. For example, consider:

class Factory<TItem> where TItem : new()

void Foo<T>(Factory<T> factory) where T : struct

If you try to call Foo<object>(null), the method will still be part of the candidate set – because when TItem is object, the constraint expressed in Factory<TItem> still holds, and that’s what’s checked when building up the candidate set. If this turns out to be the best method, it will then fail validation later, near the end of 7.6.5.1:

If the best method is a generic method, the type arguments (supplied or inferred) are checked against the constraints (ยง4.4.4) declared on the generic method. If any type argument does not satisfy the corresponding constraint(s) on the type parameter, a binding-time error occurs.

Eric’s blog post contains more detail on this.

Leave a Comment