Вы можете посмотреть, как компилятор понижает этот код:
int? a = 3;
Sample<int> sampleA = a;
в это :
int? nullable = 3;
int? nullable2 = nullable;
Sample<int> sample = nullable2.HasValue ? ((Sample<int>)nullable2.GetValueOrDefault()) : null;
Поскольку Sample<int>
является классом, его экземпляру может быть присвоено нулевое значение, и с таким неявным оператором также может быть назначен базовый тип обнуляемого объекта. Так что назначения, подобные этим, действительны:
int? a = 3;
int? b = null;
Sample<int> sampleA = a;
Sample<int> sampleB = b;
Если Sample<int>
будет struct
, это, конечно, даст ошибку.
EDIT:
Так почему это возможно? Я не смог найти его в спецификации, потому что это преднамеренное нарушение спецификации, и это только для обратной совместимости. Вы можете прочитать об этом в код :
НАРУШЕНИЕ НАРУШЕНИЯ СПЕКА:
Собственный компилятор допускает «отмененное» преобразование, даже когда возвращаемый тип преобразования не является необнуляемым типом значения. Например, если у нас есть преобразование из структуры S в строку, то «отмененное» преобразование из S? to string считается существующим компилятором с семантикой «s.HasValue? (string) s.Value: (string) null». Компилятор Roslyn увековечивает эту ошибку ради обратной совместимости.
Вот как эта "ошибка" реализована в Roslyn:
В противном случае, если возвращаемый тип преобразования имеет тип значения NULL, ссылочный тип или тип указателя P, то мы уменьшаем его как:
temp = operand
temp.HasValue ? op_Whatever(temp.GetValueOrDefault()) : default(P)
Таким образом, в соответствии с spec для данного пользовательского оператора преобразования T -> U
существует оператор с поднятыми значениями T? -> U?
, где T
и U
являются необнуляемыми типами значений. Однако такая логика также реализована для оператора преобразования, где U
является ссылочным типом по вышеуказанной причине.
ЧАСТЬ 2 Как предотвратить компиляцию кода в этом сценарии? Ну, есть способ. Вы можете определить дополнительный неявный оператор специально для обнуляемого типа и украсить его атрибутом Obsolete
. Для этого необходимо, чтобы параметр типа T
был ограничен struct
:
public class Sample<T> where T : struct
{
...
[Obsolete("Some error message", error: true)]
public static implicit operator Sample<T>(T? value) => throw new NotImplementedException();
}
Этот оператор будет выбран в качестве первого оператора преобразования для обнуляемого типа, поскольку он более конкретен.
Если вы не можете сделать такое ограничение, вы должны определить каждый оператор для каждого типа значения отдельно (если вы действительно определились, вы можете воспользоваться возможностью отражения и генерации кода с использованием шаблонов):
[Obsolete("Some error message", error: true)]
public static implicit operator Sample<T>(int? value) => throw new NotImplementedException();
Это может привести к ошибке, если в любом месте кода есть ссылка:
Ошибка CS0619 «Sample.implicit operator Sample (int?)» Устарела: «Некоторое сообщение об ошибке»