С# Почему этот тип метода вывода выводится неоднозначно?

Следующая пара функций пытается реплицировать нулевой условный оператор, доступный в С# 6.0:

public static TResult Bind<T, TResult>(this T obj, Func<T, TResult> func)
    where T : class
{
    return obj == null ? default(TResult) : func(obj);
}

public static TResult Bind<T, TResult>(this Nullable<T> obj, Func<T, TResult> func)
    where T : struct
{
    return obj.HasValue ? func(obj.Value) : default(TResult);
}

Первая функция ограничена классами, а для String s позволяет написать что-то вроде:

var x = s.Bind(a => a.Substring(1));

Вторая функция - это проблема, с которой я сталкиваюсь. Например, для a int? number я хотел бы написать:

var y = number.Bind(a => a + 1);

Однако это дает мне следующую ошибку:

Вызов неоднозначен между следующими методами или свойствами: 'BindingExtensions.Bind < T, TResult > (T, Func < T, TResult > )' и 'BindingExtensions.Bind < T, TResult > (T?, Func < T, TResult > ) '

Я предполагаю, что это имеет какое-то отношение к взаимодействию между типом вывода анонимной функции и разрешением перегрузки метода. Если я укажу тип a как int, чем он компилируется просто отлично.

var y = number.Bind((int a) => a + 1);

Однако это явно меньше желаемого. Может ли кто-нибудь сказать мне, почему компилятор считает, что вышеупомянутый вызов привязки неоднозначен и/или предлагает способ исправить это? Я знаю, что могу просто назвать две функции по-разному, но что это за забава?

Ответ 1

Перегруженные функции не могут быть устранены ограничениями типа (см. " "Общие ограничения" , где T: struct и где T: class" ). Любой тип N с нулевым значением удовлетворяет N : T и N : Nullable<T>, требуемому соответственно последним и последним Bind. Я предполагаю, что number имеет тип Nullable<int> или аналогичный.

var x = s.Bind(a => a.Substring(1));

Это однозначно, потому что s имеет тип string и для всех T не string : Nullable<T>, поэтому допустима только первая перегрузка.

var y = number.Bind(a => a + 1);

Это неоднозначно, потому что тип a => a + 1 может быть выведен как либо Func<int?,int?>, либо Func<int,int>. Если предполагается, что в качестве Func<int?,int?> применяется первая перегрузка, и если она определена как Func<int,int>, применяется вторая перегрузка.

var y = number.Bind((int a) => a + 1);

Это недвусмысленно, если number имеет тип Nullable<int>, например. Для первой перегрузки для всех T не T : Nullable<int> и T : int, поэтому она не применяется. Для второй перегрузки вам просто нужен T : int, который легко выполняется T = int.

Ответ 2

Попробуйте следующее:

    public static TResult Bind<T, TResult>(this T? obj, Func<T?, TResult> func)
    where T : struct
    {
        return obj.HasValue ? func(obj.Value) : default(TResult);
    }