C# универсальное ограничение типа для всего обнуляемого
Итак, у меня есть этот класс:
public class Foo<T> where T : ???
{
private T item;
public bool IsNull()
{
return item == null;
}
}
Теперь я ищу ограничение типа, которое позволяет мне использовать все как параметр типа, который может быть null
, Это означает, что все ссылочные типы, а также все Nullable
(T?
) типы:
Foo<String> ... = ...
Foo<int?> ... = ...
должно быть возможно.
С помощью class
поскольку ограничение типа позволяет мне использовать только ссылочные типы.
Дополнительная информация: я пишу приложение для труб и фильтров и хочу использовать null
ссылка как последний элемент, который проходит в конвейер, так что каждый фильтр может корректно завершить работу, выполнить очистку и т. д.
7 ответов
Если вы хотите выполнить проверку во время выполнения в конструкторе Foo, а не во время компиляции, вы можете проверить, является ли тип ссылочным или обнуляемым, и вызвать исключение, если это так.
Я понимаю, что только проверка во время выполнения может быть неприемлемой, но на всякий случай:
public class Foo<T>
{
private T item;
public Foo()
{
var type = typeof(T);
if (Nullable.GetUnderlyingType(type) != null)
return;
if (type.IsClass)
return;
throw new InvalidOperationException("Type is not nullable or reference type.");
}
public bool IsNull()
{
return item == null;
}
}
Затем компилируется следующий код, но последний (foo3
) создает исключение в конструкторе:
var foo1 = new Foo<int?>();
Console.WriteLine(foo1.IsNull());
var foo2 = new Foo<string>();
Console.WriteLine(foo2.IsNull());
var foo3= new Foo<int>(); // THROWS
Console.WriteLine(foo3.IsNull());
Я не знаю, как реализовать эквивалент ИЛИ в дженериках. Однако я могу предложить использовать ключевое слово по умолчанию для создания нулевого значения для типов, допускающих значения NULL, и значения 0 для структур:
public class Foo<T>
{
private T item;
public bool IsNullOrDefault()
{
return Equals(item, default(T));
}
}
Вы также можете реализовать свою версию Nullable:
class MyNullable<T> where T : struct
{
public T Value { get; set; }
public static implicit operator T(MyNullable<T> value)
{
return value != null ? value.Value : default(T);
}
public static implicit operator MyNullable<T>(T value)
{
return new MyNullable<T> { Value = value };
}
}
class Foo<T> where T : class
{
public T Item { get; set; }
public bool IsNull()
{
return Item == null;
}
}
Пример:
class Program
{
static void Main(string[] args)
{
Console.WriteLine(new Foo<MyNullable<int>>().IsNull()); // true
Console.WriteLine(new Foo<MyNullable<int>> {Item = 3}.IsNull()); // false
Console.WriteLine(new Foo<object>().IsNull()); // true
Console.WriteLine(new Foo<object> {Item = new object()}.IsNull()); // false
var foo5 = new Foo<MyNullable<int>>();
int integer = foo5.Item;
Console.WriteLine(integer); // 0
var foo6 = new Foo<MyNullable<double>>();
double real = foo6.Item;
Console.WriteLine(real); // 0
var foo7 = new Foo<MyNullable<double>>();
foo7.Item = null;
Console.WriteLine(foo7.Item); // 0
Console.WriteLine(foo7.IsNull()); // true
foo7.Item = 3.5;
Console.WriteLine(foo7.Item); // 3.5
Console.WriteLine(foo7.IsNull()); // false
// var foo5 = new Foo<int>(); // Not compile
}
}
Я столкнулся с этой проблемой в более простом случае, когда мне нужен универсальный статический метод, который мог бы принимать что-либо "обнуляемое" (либо ссылочные типы, либо Nullables), что привело меня к этому вопросу без удовлетворительного решения. Таким образом, я придумал свое собственное решение, которое было относительно легче решить, чем заданный ОП вопрос, просто имея два перегруженных метода, один из которых требует T
и имеет ограничение where T : class
и другой, который занимает T?
и имеет where T : struct
,
Затем я понял, что решение также может быть применено к этой проблеме, чтобы создать решение, которое можно проверить во время компиляции, сделав конструктор частным (или защищенным) и используя статический метод фабрики:
//this class is to avoid having to supply generic type arguments
//to the static factory call (see CA1000)
public static class Foo
{
public static Foo<TFoo> Create<TFoo>(TFoo value)
where TFoo : class
{
return Foo<TFoo>.Create(value);
}
public static Foo<TFoo?> Create<TFoo>(TFoo? value)
where TFoo : struct
{
return Foo<TFoo?>.Create(value);
}
}
public class Foo<T>
{
private T item;
private Foo(T value)
{
item = value;
}
public bool IsNull()
{
return item == null;
}
internal static Foo<TFoo> Create<TFoo>(TFoo value)
where TFoo : class
{
return new Foo<TFoo>(value);
}
internal static Foo<TFoo?> Create<TFoo>(TFoo? value)
where TFoo : struct
{
return new Foo<TFoo?>(value);
}
}
Теперь мы можем использовать это так:
var foo1 = new Foo<int>(1); //does not compile
var foo2 = Foo.Create(2); //does not compile
var foo3 = Foo.Create(""); //compiles
var foo4 = Foo.Create(new object()); //compiles
var foo5 = Foo.Create((int?)5); //compiles
Если вам нужен конструктор без параметров, вы не получите тонкости перегрузки, но вы все равно можете сделать что-то вроде этого:
public static class Foo
{
public static Foo<TFoo> Create<TFoo>()
where TFoo : class
{
return Foo<TFoo>.Create<TFoo>();
}
public static Foo<TFoo?> CreateNullable<TFoo>()
where TFoo : struct
{
return Foo<TFoo?>.CreateNullable<TFoo>();
}
}
public class Foo<T>
{
private T item;
private Foo()
{
}
public bool IsNull()
{
return item == null;
}
internal static Foo<TFoo> Create<TFoo>()
where TFoo : class
{
return new Foo<TFoo>();
}
internal static Foo<TFoo?> CreateNullable<TFoo>()
where TFoo : struct
{
return new Foo<TFoo?>();
}
}
И используйте это так:
var foo1 = new Foo<int>(); //does not compile
var foo2 = Foo.Create<int>(); //does not compile
var foo3 = Foo.Create<string>(); //compiles
var foo4 = Foo.Create<object>(); //compiles
var foo5 = Foo.CreateNullable<int>(); //compiles
У этого решения есть несколько недостатков, во-первых, вы можете предпочесть использование "new" для создания объектов. Другое, что вы не сможете использовать Foo<T>
в качестве аргумента универсального типа для ограничения типа чего-то вроде: where TFoo: new()
, Наконец, здесь нужно немного дополнительного кода, который будет увеличиваться, особенно если вам нужно несколько перегруженных конструкторов.
Как уже упоминалось, вы не можете проверить его во время компиляции. Общие ограничения в.NET крайне отсутствуют и не поддерживают большинство сценариев.
Однако я считаю, что это лучшее решение для проверки во время выполнения. Он может быть оптимизирован во время компиляции JIT, так как они оба константы.
public class SomeClass<T>
{
public SomeClass()
{
// JIT-compile time check, so it doesn't even have to evaluate.
if (default(T) != null)
throw new InvalidOperationException("SomeClass<T> requires T to be a nullable type.");
T variable;
// This still won't compile
// variable = null;
// but because you know it's a nullable type, this works just fine
variable = default(T);
}
}
Я использую
public class Foo<T> where T: struct
{
private T? item;
}
Такое ограничение типа невозможно. В соответствии с документацией о типовых ограничениях не существует ограничения, которое охватывает как обнуляемые, так и ссылочные типы. Так как ограничения могут быть объединены только в соединении, нет способа создать такое ограничение по комбинации.
Тем не менее, для ваших нужд можно использовать параметр типа неограниченного доступа, поскольку вы всегда можете проверить наличие == null. Если тип является типом значения, проверка всегда будет иметь значение false. Тогда вы, возможно, получите предупреждение R# "Возможное сравнение типа значения с нулем", что не критично, если семантика подходит именно вам.
Альтернативой может быть использование
object.Equals(value, default(T))
вместо проверки нуля, так как по умолчанию (T), где T: класс всегда равен нулю. Это, однако, означает, что вы не можете различить погоду, если необнуляемое значение никогда не устанавливалось явно или было просто установлено в значение по умолчанию.
Если вы хотите разрешить только типы значений, допускающие значение NULL, и ссылочные типы и запретить типы значений, не допускающие значения NULL, то я думаю, что вам не повезло с C# 9.
Я пишу приложение каналов и фильтров и хочу использовать нулевую ссылку в качестве последнего элемента, который проходит в конвейер, чтобы каждый фильтр мог корректно отключаться, выполнять очистку и т. Д.
Другими словами, вам нужно зарезервировать специальное значение, которое указывает конец потока.
Рассмотрите возможность создания типа оболочки, обеспечивающего это. Это было бы похоже на то, как
Nullable<T>
реализован, и имеет дополнительное преимущество, позволяя использовать не конец потока
null
значение, которое нужно передать, если это будет полезно.
public readonly struct StreamValue<T>
{
public bool IsEndOfStream { get; }
public T Value { get; }
}
public class Foo<T>
{
private T item;
public Foo(T item)
{
this.item = item;
}
public bool IsNull()
{
return object.Equals(item, null);
}
}
var fooStruct = new Foo<int?>(3);
var b = fooStruct.IsNull();
var fooStruct1 = new Foo<int>(3);
b = fooStruct1.IsNull();
var fooStruct2 = new Foo<int?>(null);
b = fooStruct2.IsNull();
var fooStruct3 = new Foo<string>("qqq");
b = fooStruct3.IsNull();
var fooStruct4 = new Foo<string>(null);
b = fooStruct4.IsNull();