C#genericstypes约束的一切可为空

所以我有这个class:

public class Foo<T> where T : ??? { private T item; public bool IsNull() { return item == null; } } 

现在我正在寻找一个types约束,允许我使用所有的types参数,可以为null 。 这意味着所有的引用types,以及所有的NullableT? )types:

 Foo<String> ... = ... Foo<int?> ... = ... 

应该是可以的。

使用class作为types约束只允许我使用引用types。

附加信息:我正在编写一个pipe道和filter应用程序,并希望使用null引用作为传递到pipe道中的最后一个项目,以便每个filter都可以很好地closures,执行清理等操作。

如果您愿意在Foo的构造函数中进行运行时检查,而不是进行编译时检查,则可以检查该types是否不是引用types或可为空types,如果是这种情况,则会引发exception。

我意识到,只有运行时检查可能是不可接受的,但以防万一:

 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 )在构造函数中抛出一个exception:

 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()); 

我不知道如何在generics中实现等价于OR 。 不过,我可以build议使用默认的关键字来为可为空的types创buildnull,为结构创build0值:

 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 } } 

这种types约束是不可能的。 根据types约束的文档,没有捕获可为空和引用types的约束。 由于约束只能组合在一起,因此无法通过组合来创build这样的约束。

但是,您可以根据您的需要回退到不受约束的types参数,因为您始终可以检查== null。 如果这个types是一个值types,那么这个检查总是会被计算为false。 那么你可能会得到R#警告“可能比较值types与空值”,这并不重要,只要语义是适合你的。

另一种可能是使用

 object.Equals(value, default(T)) 

而不是空检查,因为默认(T)其中T:类始终为空。 但是,这意味着您无法区分weather是否从未明确设置过非空值,或者只是将其设置为其默认值。

如上所述,你不能有一个编译时检查它。 .NET中的通用约束严重缺乏,不支持大多数场景。

不过,我认为这是运行时检查的更好的解决scheme。 它可以在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; } 

我遇到了这个问题,想要一个更简单的情况下想要一个generics静态方法,可以采取任何“可空”(无论是引用types或可空),这使我没有满意的解决scheme的这个问题。 所以我提出了自己的解决scheme,这个解决scheme比OP所说的问题要简单得多,只需要两个重载的方法,一个取T ,另一个取T的约束T? 并且有where T : struct

然后我意识到,解决scheme也可以应用于这个问题来创build一个解决scheme,通过使构造函数私有(或保护),并使用静态工厂方法在编译时可检查:

  //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 

这个解决scheme有几个缺点,一个是你可能更喜欢用'new'来构造对象。 另一个是你不能使用Foo<T>作为types约束的genericstypes参数,如: where TFoo: new() 。 最后是你需要额外的代码,这将增加特别是如果你需要多重载构造函数。

  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();