所以我有这个课程:
public class Foo<T> where T : ???
{
private T item;
public bool IsNull()
{
return item == null;
}
}
现在我正在寻找一种类型约束,允许我将所有内容用作类型参数null
。这意味着所有引用类型以及所有Nullable
(T?
)类型:
Foo<String> ... = ...
Foo<int?> ... = ...
应该是可能的。
使用class
作为类型约束只允许我使用引用类型。
其他信息:
我正在编写一个管道和过滤器应用程序,并希望使用null
引用作为传入管道的最后一项,这样每个过滤器都可以很好地关闭,进行清理等等......
答案 0 :(得分:21)
如果您愿意在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());
答案 1 :(得分:15)
我不知道如何在泛型中实现等效于 OR 。但是,我可以建议使用默认关键字,以便为可空类型创建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
}
}
答案 2 :(得分:9)
我遇到了一个更简单的例子,想要一个通用的静态方法,可以采取任何“可以为空的”(引用类型或Nullables),这让我对这个问题没有满意的解决方案。所以我提出了我自己的解决方案,它比OP的陈述问题更容易解决,只需要两个重载方法,一个采用T
并且具有约束where T : class
而另一个采用T?
{1}}并且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()
。最后是你需要的额外代码,如果你需要多个重载的构造函数,这将会增加。
答案 3 :(得分:6)
如前所述,您无法进行编译时检查。 .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);
}
}
答案 4 :(得分:3)
这种类型约束是不可能的。根据{{3}},没有约束可以捕获可空和引用类型。由于约束只能在连接中组合,因此无法通过组合创建此类约束。
但是,您可以根据需要回退到无约束类型参数,因为您始终可以检查== null。如果类型是值类型,则检查将始终评估为false。那么你可能会得到R#警告“值类型与null的可能比较”,这并不重要,只要语义适合你。
另一种方法是使用
object.Equals(value, default(T))
而不是null检查,因为默认(T)其中T:class始终为null。但是,这意味着您无法区分天气,从未明确设置过的非可空值或仅设置为默认值。
答案 5 :(得分:3)
我用
public class Foo<T> where T: struct
{
private T? item;
}
答案 6 :(得分:0)
如果您只想允许可为空的值类型和引用类型,并禁止非可为空的值类型,那么从C#9开始,我认为您很不走运。
我正在编写管道和过滤器应用程序,并希望使用空引用作为传递到管道中的最后一项,以便每个过滤器都能正常关闭,进行清理等。
换句话说,您需要保留一个表示流结束的特殊值。
考虑创建提供此功能的包装器类型。它与Nullable<T>
的实现方式相似,并具有额外的好处,即如果有用的话,允许传输非流结束null
值。
public readonly struct StreamValue<T>
{
public bool IsEndOfStream { get; }
public T Value { get; }
}
答案 7 :(得分:-2)
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();