Как вернуть значение T, допускающее значение NULL, из универсального метода, где T: notnull?

Рассмотрим этот метод в проекте с включенным nullable:

public C? GetComponentOrNull<C>() where C : notnull;

Цель состоит в том, чтобы C был типом, не допускающим значения NULL (будь то тип значения или ссылочный тип, не допускающий значения NULL), но функция возвращала версию этого типа, допускающую значение NULL.

Но, видимо, это работает не так, как я хочу:

var component = GetComponentOrNull<int>();
if (component != null) {
    // ...
}

Я получаю предупреждение о сравнении null:

Результатом выражения всегда является «ложь», поскольку значение типа «int» никогда не равно «null» типа (CS0472). «int?»

Наведение курсора на сайт вызова показывает, что предполагаемый тип возвращаемого значения — int, а не int? или Nullable<int>, как я ожидал.

Я попробовал написать Nullable<C> в качестве возвращаемого типа вместо C?, но получил ошибку:

Тип «C» должен быть типом значения, не допускающим значения NULL, чтобы использовать его в качестве параметра «T» в универсальном типе или методе «Nullable» (CS0453).

Что делать? Могу ли я написать это так, как я хотел?

Используете ли вы <Nullable>enable</Nullable> в проекте?

Magnus 08.04.2024 10:57

Конечно. Но я забыл упомянуть об этом. Редактирование.

Thomas 08.04.2024 11:05

Тогда зачем вам указывать notnull? C уже не равно нулю.

Magnus 08.04.2024 11:09

Я думаю, поскольку типы значений и ссылочные типы работают по-разному, вам либо нужны два метода, либо я предпочитаю такую ​​сигнатуру метода public bool TryGetComponent<C>(out C? component) { ... }

DavidG 08.04.2024 11:40

@DavidG Стандартная библиотека .NET, похоже, делает это именно так, так что, возможно, это правильный путь. Не стесняйтесь опубликовать это в качестве ответа.

Thomas 08.04.2024 11:48

См. этот ответ, который объясняет такое поведение. Короче говоря, если T — это тип значения, T и T? — один и тот же тип.

Guru Stron 08.04.2024 13:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
104
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

'?' имеет разные значения для типов классов и структур, поэтому вам придется использовать два метода отдельно для класса и структуры, например public C? GetComponentOrNull<C>() where C : class и where C : struct.

К сожалению, компилятор этого не допустит, поскольку оба метода имеют одинаковую сигнатуру (даже если их общие ограничения не перекрываются). Мне пришлось бы дать им разные имена. Я думаю, это работает, но это неуклюже. Оставлю этот вопрос открытым на случай, если у кого-то есть более элегантный ответ...

Thomas 08.04.2024 11:46

@Thomas Есть несколько хаков, но они АБСОЛЮТНО НЕ рекомендуются. Если вам действительно интересно, вы можете попробовать добавить два метода с помощью where C : class и where C : struct, а затем добавить к обоим методам параметр со значением по умолчанию: , C? dummy = default. Это обманом заставляет компилятор выбрать правильную сигнатуру, но вы «загрязняете» сигнатуры дополнительным параметром, который не собираетесь использовать.

Lasse V. Karlsen 08.04.2024 13:43

@Лампа права. @Томас: если хочешь кровавых подробностей, посмотри этот недавний ответ. Для универсальных типов CLR не позволяет обходного пути. Однако для методов обходной путь Лассе — перегруженные методы с фиктивным параметром — работает, и я бы полностью на него пошел. Я использую аналогичные фиктивные параметры, чтобы обойти недостатки механизма вывода обобщенных типов компилятора C#.

Anton Tykhyy 08.04.2024 14:49

@Томас, хитрость в том, чтобы поместить результат в качестве выходного параметра вместо возвращаемого, GetComponentOrNull<C>(out C? value)

Lamp 09.04.2024 03:38
Ответ принят как подходящий

Кажется, что суффикс ?, допускающий значение NULL, удаляется компилятором при понижении кода, который. Вы можете увидеть, как это работает здесь. Обратите внимание, что в сокращенном коде метод на самом деле не возвращает значение, допускающее значение NULL, и даже оператор if удален. Однако если вы ограничите общий тип struct, компилятор теперь знает, как переписать код.

Более безопасный и (возможно) более читаемый способ написания кода — это позаимствовать то, как BCL делает подобные вещи, и использовать подход Try...(our T value). Например:

public bool TryGetComponent<C>(out C? component)
{
    component = default!;
    return false;
}

Просто чтобы прояснить это, параметр component выше также будет 0 при использовании <int> в этом случае, с where C : notnull или без него, просто вам больше не нужно использовать возвращаемое значение null vs non-null, чтобы указать, получили ли вы компонент или нет. .

Lasse V. Karlsen 08.04.2024 13:40

@LasseV.Karlsen Спасибо, да, отличное разъяснение.

DavidG 08.04.2024 14:53

Это эзотерический, АБСОЛЮТНО НЕ РЕКОМЕНДУЕМЫЙ ответ. Не стесняйтесь проголосовать против этого и предать забвению.

Вы можете «обмануть» компилятор, добавив два метода, но это больше похоже на «Посмотрите, какой я умный», чем на «Вот как я бы это сделал». Я бы абсолютно согласился с кодом в ответе DavidG.

В любом случае, если вы хотите навредить себе, вот код:

var t = new Test();

t.GetComponentOrNull<int>();
t.GetComponentOrNull<string>();

public class Test
{
    public C? GetComponentOrNull<C>(C? dummy = default)
        where C : class
    {
        Console.WriteLine("returning class");
        return default;
    }

    public C? GetComponentOrNull<C>(C? dummy = default)
        where C : struct
    {
        Console.WriteLine("returning struct");
        return default;
    }
}

Распечатывает:

returning struct
returning class

Наличие двух фиктивных параметров заставляет компилятор выбрать правильную перегрузку. Вы, конечно, загрязняете свои перегрузки метода дополнительными версиями только для того, чтобы обойти это, и этот параметр также будет отображаться в intellisense.

Опять же, НЕ РЕКОМЕНДУЕТСЯ.

Спасибо! Даже если это не очень хорошее решение, оно все равно поучительно.

Thomas 08.04.2024 14:00

Ха, я никогда бы не подумал, что это сработает. Интересный хак!

DavidG 08.04.2024 14:51

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