public class ClassA<TUid>
{
public TUid? Uid { get; set; }
}
public class ClassB : ClassA<Guid>
{
public void Test()
{
ClassB dto = new ClassB();
dto.Uid = null;
}
}
Компилятор C# не принимает этот код C# в строке: dto.Uid = null говоря, что
Невозможно преобразовать значение null в Guid, поскольку это тип значения, не допускающий значения NULL.
Разве общий тип TUid в ClassA не заменяется на Guid в ClassB, поэтому в результате должно получиться Guid? вот так?
public Guid? Uid { get; set; }
Если я объявлю ClassA неуниверсальным
public class ClassA
{
public TUid? Uid { get; set; }
}
или передайте общий параметр как Guid?
public class ClassB : ClassA<Guid?>
тогда dto.Uid = null не вызывает никаких проблем.
Что такого особенного в правилах вывода типов параметров, которые сбивают с толку компилятор в первом примере?





Спецификатор ? в дженериках — это… весело. С ограничением where T : struct: да, это означает Nullable<T> - но у вас его нет, что означает, что T? на самом деле означает «для целей отслеживания нулевого ссылочного типа (NRT) этот тип следует считать обнуляемым», что запускает компилятор. предлагать null тесты для таких вещей, как Uid.Whatever(), и влияет на разрешенные параметры и назначения членов внешним вызывающим объектом, если T является class или interface - например, для ClassA<string> и TUid? Uid он считает obj.Uid = null действительным, тогда как с TUid Uid : obj.Uid = null вызовет предупреждение НЗТ.
Если вы хотите, чтобы этот тип всегда использовался с типами значений: добавьте where T : struct
Если вы хотите оставить этот тип общим, но использовать Guid? в данном конкретном случае, используйте : ClassA<Guid?>.
Имейте в виду, что поддержка NRT появилась относительно недавно и в основном влияет на компилятор. Фактическая среда выполнения мало что знает (если вообще что-то знает) о НЗТ. До появления NRT синтаксис T? был даже незаконен без ограничения where T : struct, и эта основная реальность — это то, с чем вы на самом деле работаете.
Это связано с тем, что T? имеет разное значение в зависимости от того, является ли T ссылочным типом или типом значения.
Если T является типом значения, использование T? фактически оборачивает переменную в другую структуру - Nullable<T> struct.
Однако если T является ссылочным типом, использование T? все равно будет тем же ссылочным типом, только теперь вы сообщаете компилятору, что он может быть нулевым.
Когда компилятор видит T? и T — это структура, он понижает код до Nullable<T> — однако, если T — это класс, он не может этого сделать, потому что Nullable<T> может принимать структуры только как T.
Если вы не используете общие ограничения, поведение по умолчанию похоже на использование ограничения класса, поэтому
class C<T>
{
public T? Value {get;set;}
}
опускается так же, как
class C<T> where T : class
{
public T? Value {get;set;}
}
к этому:
[NullableContext(2)]
[Nullable(0)]
internal class C<T>
{
[CompilerGenerated]
private T <Value>k__BackingField;
public T Value
{
[CompilerGenerated]
get
{
return <Value>k__BackingField;
}
[CompilerGenerated]
set
{
<Value>k__BackingField = value;
}
}
}
Однако когда вы добавляете ограничение struct,
этот
class C<T> where T : struct
{
public T? Value {get;set;}
}
снижается до этого:
internal class C<T> where T : struct
{
[CompilerGenerated]
private Nullable<T> <Value>k__BackingField;
public Nullable<T> Value
{
[CompilerGenerated]
get
{
return <Value>k__BackingField;
}
[CompilerGenerated]
set
{
<Value>k__BackingField = value;
}
}
}
Вы можете поиграть с ним на SharpLab.IO
Ваш тип
GuidнеNullable<Guid>. Попробуйте, например,where TUid: structи посмотрите, что произойдет.