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引用作为传递到管道中的最后一项,以便每个过滤器都可以很好地关闭,进行清理等。.

66710 次浏览

我不知道如何在泛型中实现与 或者等价的东西。然而,我可以建议使用 违约关键字,以便为可空类型创建 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
}
}

这样的类型约束是不可能的。根据 类型约束的文档化,不存在同时捕获可空类型和引用类型的约束。由于约束只能在连词中组合,因此无法通过组合创建这样的约束。

但是,由于您总是可以检查 = = null,因此可以根据需要回退到无约束类型参数。如果类型是值类型,那么检查的值总是为 false。然后您可能会得到 R # 警告“可能比较值类型与 null”,这并不重要,只要语义适合您。

另一种选择是

object.Equals(value, default(T))

而不是 null 检查,因为 default (T)其中 T: class 始终为 null。但是,这意味着您无法区分从未显式设置过非空值或只是将其设置为默认值的天气。

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

如果您愿意在 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());

正如前面提到的,您不能对它进行编译时检查。中的一般约束。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;
}

我遇到这个问题是因为一个更简单的情况,我想要一个通用的静态方法,它可以接受任何“可为空”的东西(引用类型或者可为空的东西) ,这让我遇到了这个问题,但没有令人满意的解决方案。所以我想出了我自己的解决方案,它比 OP 提出的问题更容易解决,只需要两个重载的方法,一个接受 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”来构造对象。另一个问题是,对于类似于 where TFoo: new()的类型约束,不能使用 Foo<T>作为泛型类型参数。最后是这里需要的额外代码,如果需要多个重载构造函数,这些代码将会增加。

如果您只想允许可空值类型和引用类型,并且不允许非可空值类型,那么我认为从 C # 9开始您就不走运了。

我正在编写一个管道和过滤器应用程序,希望使用 null 引用作为传递到管道中的最后一个项,这样每个过滤器都可以很好地关闭,进行清理等等。.

换句话说,您需要保留一个指示流结束的特殊值。

考虑创建提供此。它类似于 Nullable<T>的实现方式,如果有用的话,它还有一个额外的好处,即允许传输非流结束 null值。

public readonly struct StreamValue<T>
{
public bool IsEndOfStream { get; }
public T Value { get; }
}