Search code examples
c#generics.net-4.0ambiguousoverload-resolution

Generic method overload ambiguous with nullable types


Say I have two generic, overloaded methods of the form:

public string Do<T>(T maybeValue, Func<T, string> func)
  where T : class
{
  if(maybeValue == null) return null;
  return func(maybeValue);
}

public string Do<T>(T? maybeValue, Func<T, string> func)
  where T : struct
{
  if(!maybeValue.HasValue) return null;
  return func(maybeValue.Value);
}

Now, calling the method with a variable of type nullable, C# refuses to compile and says that the call is ambiguous between the two overloads:

int? maybeX = 3;
Do(maybeX, x => x.ToString());

The call is ambiguous between the following methods or properties: 'Program.Do<int?>(int?, System.Func<int?,string>)' and 'Program.Do<int>(int?, System.Func<int,string>)'

Easy fixes are to include the generic parameter when calling the method, or to specify the type of the lambda argument:

Do<int>(maybeX, x => x.ToString());
Do(maybeX, (int x) => x.ToString());

Interestingely, choosing int? as generic type during invocation will not compile

The type must be a reference type in order to use it as parameter 'T' in the generic type or method".

How come? Obviously only one of the two overloads can be used with a value of type int?, yet the compiler says the call is ambiguous. Can I further constrain the methods to help the compiler decide which method to call, without having the invoking code specify the type explicitely?


Solution

  • The where T : class constraint is not part of the signature of the method. That check happens later in the method overload selection process.
    That's why it is considered ambiguous. Both methods are a match before any constraints are checked.
    If you explicitly say Do<int?> only the first method is a match, but then the constraint 'kicks in' and determines it is invalid because int? is not a reference type.

    The second method will be chosen if you change it to:

    public static string Do<T>(T? maybeValue, Func<T?, string> func)
        where T : struct