#c# #generics #nullable
Вопрос:
Учитывая следующую общую Foo1
функцию:
struct Key<T> {}
static readonly Key<double> MyKey = new Key<double>();
T? Foo1<T>(Key<T> key)
{
return defau<
}
Наивный читатель предположил бы, что:
var foo1 = Foo1(MyKey);
foo1
имеет тип double?
, получается, что компилятор выбирает double
тип возвращаемого значения. Мне нужно явно добавить ограничение, чтобы получить возвращаемое значение с возможностью обнуления:
T? Foo2<T>(Key<T> key) where T : struct // really return a nullable
{
return defau<
}
Может ли кто-нибудь объяснить, почему аннотация для ссылки с возможностью обнуления ?
не отображается в моей первой Foo1
функции ?
Комментарии:
1. Вероятно, активируются типы ссылок, допускающие значения null, иначе подпись
T? Foo2<T>(Key<T> key)
— без ограничения структуры — не компилировалась бы. Для вывода аргументов типа проверяются только входные значения и их типы во время компиляции- типы возвращаемых значений игнорируются.
Ответ №1:
Давайте начнем с некоторой предыстории:
До того, как C# 9.0 Foo1
был недействителен. Даже в C# 8.0 с включенными ссылками на обнуление:
CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type
Foo2
был действителен даже до C# 8.0, потому T?
что имел смысл только в том случае, если T
был структурой, и в этом случае T?
имел другой тип от T
( Nullable<T>
). Пока что все довольно просто.
Начиная с C# 8.0 были введены ссылки, допускающие обнуление, что вызвало некоторую путаницу. Отныне T?
может либо означать Nullable<T>
, либо просто T
. Эта версия не разрешала T?
без ограничений, но она разрешала также, когда вы указали where T : class
.
Без использования ограничений вам пришлось использовать атрибуты, чтобы указать, что T
может быть null
возвращаемым значением:
// C# 8.0: Poor man's T?
[return: MaybeNull] T Foo1<T>(Key<T> key) => defau<
И что, если T
теперь это тип значения? Он явно не изменит свой тип на Nullable<T>
в возвращаемом значении. Чтобы вернуть double?
аргумент вашего типа , он также должен быть double?
, то MyKey
есть также должен быть a Key<double?>
.
В C# 9.0 ограничение для T?
было ослаблено, теперь оно не нуждается в ограничении:
// C# 9.0: this is valid now
T? Foo1<T>(Key<T> key) => defau<
Но теперь это по сути означает то же самое, что и версия C# 8.0. Без where T : struct
ограничения T?
это тот же тип , T
что и так, это не что иное, как указание на то, что результат может быть null
, что может появиться в предупреждениях компилятора. Чтобы возвращать типы значений, допускающие значение null, вы должны использовать double?
в качестве общего аргумента, что также означает, что у вас Key
также должен быть определен тип, допускающий значение null:
static readonly Key<double?> MyKey = new Key<double?>();
Если в вашем случае ключ, допускающий обнуление, не имеет смысла, то вы не можете ничего сделать, кроме как указать where T : struct
ограничение, как в Foo2
старом правиле: T?
и T
иметь разные типы, где T?
это означает Nullable<T>
.
Обновление: Основное различие между Foo1
и Foo2
, возможно, более очевидно, если вы увидите их декомпилированный исходный код:
[System.Runtime.CompilerServices.NullableContext(2)]
private static T Foo1<T>([System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})] Key<T> key)
{
return default(T);
}
private static Nullable<T> Foo2<T>(Key<T> key) where T : struct
{
return null;
}
Обратите внимание, что возвращаемый тип Foo1
просто T
с некоторой аннотацией, поэтому компилятор может выдавать соответствующие предупреждения.