Рассмотрим этот метод в проекте с включенным 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).
Что делать? Могу ли я написать это так, как я хотел?
Конечно. Но я забыл упомянуть об этом. Редактирование.
Тогда зачем вам указывать notnull? C
уже не равно нулю.
Я думаю, поскольку типы значений и ссылочные типы работают по-разному, вам либо нужны два метода, либо я предпочитаю такую сигнатуру метода public bool TryGetComponent<C>(out C? component) { ... }
@DavidG Стандартная библиотека .NET, похоже, делает это именно так, так что, возможно, это правильный путь. Не стесняйтесь опубликовать это в качестве ответа.
См. этот ответ, который объясняет такое поведение. Короче говоря, если T
— это тип значения, T
и T?
— один и тот же тип.
'?' имеет разные значения для типов классов и структур, поэтому вам придется использовать два метода отдельно для класса и структуры, например public C? GetComponentOrNull<C>() where C : class
и where C : struct
.
К сожалению, компилятор этого не допустит, поскольку оба метода имеют одинаковую сигнатуру (даже если их общие ограничения не перекрываются). Мне пришлось бы дать им разные имена. Я думаю, это работает, но это неуклюже. Оставлю этот вопрос открытым на случай, если у кого-то есть более элегантный ответ...
@Thomas Есть несколько хаков, но они АБСОЛЮТНО НЕ рекомендуются. Если вам действительно интересно, вы можете попробовать добавить два метода с помощью where C : class
и where C : struct
, а затем добавить к обоим методам параметр со значением по умолчанию: , C? dummy = default
. Это обманом заставляет компилятор выбрать правильную сигнатуру, но вы «загрязняете» сигнатуры дополнительным параметром, который не собираетесь использовать.
@Лампа права. @Томас: если хочешь кровавых подробностей, посмотри этот недавний ответ. Для универсальных типов CLR не позволяет обходного пути. Однако для методов обходной путь Лассе — перегруженные методы с фиктивным параметром — работает, и я бы полностью на него пошел. Я использую аналогичные фиктивные параметры, чтобы обойти недостатки механизма вывода обобщенных типов компилятора C#.
@Томас, хитрость в том, чтобы поместить результат в качестве выходного параметра вместо возвращаемого, GetComponentOrNull<C>(out C? value)
Кажется, что суффикс ?
, допускающий значение 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
, чтобы указать, получили ли вы компонент или нет. .
@LasseV.Karlsen Спасибо, да, отличное разъяснение.
Это эзотерический, АБСОЛЮТНО НЕ РЕКОМЕНДУЕМЫЙ ответ. Не стесняйтесь проголосовать против этого и предать забвению.
Вы можете «обмануть» компилятор, добавив два метода, но это больше похоже на «Посмотрите, какой я умный», чем на «Вот как я бы это сделал». Я бы абсолютно согласился с кодом в ответе 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.
Опять же, НЕ РЕКОМЕНДУЕТСЯ.
Спасибо! Даже если это не очень хорошее решение, оно все равно поучительно.
Ха, я никогда бы не подумал, что это сработает. Интересный хак!
Используете ли вы <Nullable>enable</Nullable> в проекте?