Перегрузка параметра универсального типа и метода с возможностью обнуления

#c# #.net #generics #types #nullable

#c# #.net #универсальные методы #типы #обнуляемый

Вопрос:

Привет
У меня есть этот код, использующий универсальный и обнуляемый:

 // The first one is for class
public static TResult With<TInput, TResult>(this TInput o, 
          Func<TInput, TResult> evaluator)
    where TResult : class
    where TInput : class

// The second one is for struct (Nullable)
public static TResult With<TInput, TResult>(this Nullable<TInput> o, 
          Func<TInput, TResult> evaluator)
    where TResult : class
    where TInput : struct
  

Пожалуйста, обратите внимание на ограничение TInput: одно — class, другое — struct. Затем я использую их в:

 string s;
int? i;

// ...

s.With(o => "");
i.With(o => ""); // Ambiguos method
  

Это вызывает ошибку Ambiguos. Но у меня также есть другая пара:

 public static TResult Return<TInput, TResult>(this TInput o,
          Func<TInput, TResult> evaluator, TResult failureValue)
    where TInput : class

public static TResult Return<TInput, TResult>(this Nullable<TInput> o,
          Func<TInput, TResult> evaluator, TResult failureValue)
    where TInput : struct
  

Этот параметр успешно компилируется

 string s;
int? i;

// ...

s.Return(o => 1, 0);
i.Return(o => i   1, 0);
  

Я не получил никаких подсказок, почему это происходит. Первый вариант кажется нормальным, но выдает ошибку компиляции. Второй параметр (‘Return’) должен быть ошибкой, если первый является, но компилируется успешно. Я что-то пропустил?

Комментарии:

1. В качестве комментария к стилю я бы не стал использовать для лямбда-параметра то же имя, что и для переменной, уже находящейся в области видимости.

2. ах да, это действительно вызывает ошибку компиляции. Я просто вставляю и редактирую код в спешке.

Ответ №1:

Ограничения внутри универсального метода не учитываются при выборе перегрузки — они проверяются после выбора перегрузки.

Ограничения в пределах типов параметров проверяются как часть выбора перегрузки. Это немного сбивает с толку, но в конечном итоге имеет смысл.

У меня есть сообщение в блоге по этому поводу, которое может помочь разобраться в этом подробнее.

Дополнительно обратите внимание, что во втором примере есть дополнительный аргумент, который вносит вклад в вывод типа, что и определяет разницу между ними. TResult предполагается, что это int , что предотвращает допустимость первой перегрузки — нет преобразования из (int? x) => x 1 в Func<int?, int> , тогда как есть преобразование из (int x) => x 1 в Func<int, int> .

Комментарии:

1. Прямо сейчас я довольно доволен, и я пытаюсь понять ваш последний абзац. Оба делегата Func<> не используют nullable (int?) вообще. Только первые параметры являются.

2. @Hendry: Первая перегрузка в каждом случае будет обработана, TInput = int? потому что вы вызываете ее на int? . Следовательно, evaluator тип параметра будет Func<int?, TResult> .

3. о, я понимаю, вот почему разрешение перегрузки, основанное на лямбда-функции, завершается ошибкой при первом «возврате» и переходит ко второму. Большое спасибо.

4. @JonSkeet Пожалуйста, рассмотрите возможность обновления ссылки.