Компилятор C# не принимает значение NULL для свойства универсального типа, допускающего значение NULL

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 не вызывает никаких проблем.

Что такого особенного в правилах вывода типов параметров, которые сбивают с толку компилятор в первом примере?

Ваш тип Guid не Nullable<Guid>. Попробуйте, например, where TUid: struct и посмотрите, что произойдет.

Fildor 29.05.2024 10:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Спецификатор ? в дженериках — это… весело. С ограничением 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

Другие вопросы по теме