Обнуляемые ссылочные типы: как указать "T"? тип без ограничения класса или структуры
Я хочу создать универсальный класс, который имеет член типа T
T
может быть классом, обнуляемым классом, структурой или обнуляемой структурой. Так что в основном все. Это упрощенный пример, который показывает мою проблему:
#nullable enable
class Box<T> {
public T Value { get; }
public Box(T value) {
Value = value;
}
public static Box<T> CreateDefault()
=> new Box<T>(default(T));
}
Из-за использования новой #nullable enable
я получаю следующее предупреждение: Program.cs(11,23): warning CS8653: A default expression introduces a null value when 'T' is a non-nullable reference type.
Это предупреждение имеет смысл для меня. Затем я попытался это исправить, добавив ?
к свойству и параметру конструктора:
#nullable enable
class Box<T> {
public T? Value { get; }
public Box(T? value) {
Value = value;
}
public static Box<T> CreateDefault()
=> new Box<T>(default(T));
}
Но теперь я получаю две ошибки:
Program.cs(4,12): error CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.
Program.cs(6,16): error CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.
Тем не менее, я не хочу добавлять ограничения. Мне все равно, если T
класс или структура.
Очевидное решение - обернуть нарушающих членов в директиву #nullable disable
. Однако, как и #pragma warning disable
, я бы хотел избежать этого, если в этом нет необходимости. Есть ли другой способ заставить мой код компилироваться без отключения проверок обнуляемости или предупреждения CS8653?
$ dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 3.0.100-preview4-011223
Commit: 118dd862c8
Ответы
Ответ 1
Как обсуждалось в комментариях к этому вопросу, вам, вероятно, потребуется подумать о том, является ли Box<string>
со значением по умолчанию допустимым или нет в контексте, допускающем обнуляемость, и потенциально скорректировать поверхность API соответствующим образом. Возможно, тип должен быть Box<string?>
, чтобы экземпляр, содержащий значение по умолчанию, был действительным. Однако существуют сценарии, в которых вы хотите указать, что свойства, возвращаемые методы или параметры и т.д. Все еще могут иметь значение null, даже если они имеют необнуляемые ссылочные типы. Если вы находитесь в этой категории, вы, вероятно, захотите использовать атрибуты, связанные с обнуляемостью.
Атрибуты MaybeNull и AllowNull были введены в .NET Core 3 для обработки этого сценария.
Некоторые из специфических поведений этих атрибутов все еще развиваются, но основная идея заключается в следующем:
[MaybeNull]
означает, что вывод чего-либо (чтение поля или свойства, возврат метода и т.д.) Может быть null
.
[AllowNull]
означает, что ввод чего-либо (запись поля или свойства, параметра метода и т.д.) Может быть null
.
#nullable enable
using System.Diagnostics.CodeAnalysis;
class Box<T>
{
[MaybeNull]
public T Value { get; }
public Box([AllowNull] T value) // 1
{
Value = value;
}
public static Box<T> CreateDefault()
{
return new Box<T>(default(T)!); // 2
}
public static void UseStringDefault()
{
var box = Box<string>.CreateDefault();
_ = box.Value.Length; // 3
}
public static void UseIntDefault()
{
var box = Box<int>.CreateDefault();
_ = box.Value.ToString(); // 4
}
}
Примечание:
- Атрибуты анализа потока в настоящее время влияют только на вызовы аннотированных элементов, а не на реализацию. Это означает, что если мы удалим атрибут
[MaybeNull]
из свойства Value
, мы не получим предупреждение о назначении ему параметра [AllowNull] T value
. [AllowNull]
на практике просто предотвращает выдачу предупреждений о возможных нулевых аргументах на сайте вызовов.
- Анализ компилятором параметров неограниченного (то есть неизвестного типа значения или типа ссылки) ограничен. На этом этапе это означает, что компилятор выдает предупреждение: "Выражение по умолчанию вводит нулевое значение, когда" T "является необнуляемым ссылочным типом.". Поскольку компилятор не всегда может определить, является ли использование значения типа T безопасным для нулевого значения или нет, он вместо этого выдает предупреждения в местах, где T может быть необнуляемым ссылочным типом, но можно ввести "нулевое". Наиболее очевидным местом, где это происходит, является
default(T)
, но это также может произойти при вызове универсальных методов, чьи результаты аннотируются с помощью [MaybeNull]
. Сейчас мы решаем проблему, подавляя предупреждение с помощью default(T)!
.
- Это более простой случай, когда мы выдаем предупреждение об обнуляемости при разыменовании
box.Value
из-за аннотации [MaybeNull]
в свойстве.
- В этом случае атрибут MaybeNull не вызывает предупреждение при использовании, поскольку
box.Value
является типом значения, не допускающим значения NULL.
Пожалуйста, смотрите https://devblogs.microsoft.com/dotnet/try-out-nullable-reference-types для получения дополнительной информации, особенно в разделе "проблема с T?".
Ответ 2
Джефф Меркадо поднял хороший момент в комментариях:
Я думаю, у вас есть несколько противоречивых целей. Вы хотите иметь представление о поле по умолчанию, но для справочных типов, что еще является подходящим значением по умолчанию? Значение по умолчанию равно нулю для ссылочных типов, что напрямую конфликтует с использованием нулевых ссылочных типов. Возможно, вам нужно будет ограничить T типами, которые могут быть созданы по умолчанию (new()).
Например, default(T)
для T = string
будет null
, поскольку во время выполнения нет различия между string
и string?
, Это текущее ограничение языковой функции.
Я обошел эту проблему, создав отдельные методы CreateDefault
для каждого случая:
#nullable enable
class Box<T> {
public T Value { get; }
public Box(T value) {
Value = value;
}
}
static class CreateDefaultBox
{
public static Box<T> ValueTypeNotNull<T>() where T : struct
=> new Box<T>(default);
public static Box<T?> ValueTypeNullable<T>() where T : struct
=> new Box<T?>(null);
public static Box<T> ReferenceTypeNotNull<T>() where T : class, new()
=> new Box<T>(new T());
public static Box<T?> ReferenceTypeNullable<T>() where T : class
=> new Box<T?>(null);
}
Мне кажется, что это безопасный тип за счет более уродливых сайтов вызовов (CreateDefaultBox.ReferenceTypeNullable<object>()
вместо Box<object?>.CreateDefault()
). В примере класса, который я разместил, я бы просто полностью удалил методы и напрямую использовал конструктор Box
. Ну что ж.
Ответ 3
Как насчет сохранения его как объекта и возвращения приведения T?
public class Box<T>
{
public T Value { get { return (T)oValue; } }
private object oValue;
public Box(T value)
{
oValue = value;
}
public static Box<T> CreateDefault()
=> new Box<T>(default(T));
}
Ответ 4
class Box<T> {
public T! Value { get; }
public Box(T! value) {
Value = value;
}
public static Box<T> CreateDefault()
=> new default!;
}
Что значит ноль! выражение значит?