简体   繁体   English

一切可以为空的 C# 泛型类型约束

[英]C# generic type constraint for everything nullable

So I have this class:所以我有这门课:

public class Foo<T> where T : ???
{
    private T item;

    public bool IsNull()
    {
        return item == null;
    }

}

Now I am looking for a type constraint that allows me to use everything as type parameter that can be null .现在我正在寻找一种类型约束,它允许我将所有内容用作可以为null类型参数。 That means all reference types, as well as all the Nullable ( T? ) types:这意味着所有引用类型,以及所有Nullable ( T? ) 类型:

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

should be possible.应该是可能的。

Using class as the type constraint only allows me to use the reference types.使用class作为类型约束只允许我使用引用类型。

Additional Information: I am writing a pipes and filters application, and want to use a null reference as the last item that passes into the pipeline, so that every filter can shut down nicely, do cleanup, etc...附加信息:我正在编写一个管道和过滤器应用程序,并希望使用null引用作为传入管道的最后一项,以便每个过滤器都可以很好地关闭,进行清理等...

If you are willing to make a runtime check in Foo's constructor rather than having a compile-time check, you can check if the type is not a reference or nullable type, and throw an exception if that's the case.如果您愿意在 Foo 的构造函数中进行运行时检查而不是进行编译时检查,您可以检查该类型是否不是引用或可为空类型,如果是这种情况,则抛出异常。

I realise that only having a runtime check may be unacceptable, but just in case:我意识到只进行运行时检查可能是不可接受的,但以防万一:

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

Then the following code compiles, but the last one ( foo3 ) throws an exception in the constructor:然后编译下面的代码,但最后一个( 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());

I don't know how to implement equivalent to OR in generics.我不知道如何在泛型中实现等价于OR However I can propose to use default key word in order to create null for nullable types and 0 value for structures:但是,我可以建议使用默认关键字,以便为可空类型创建 null 并为结构创建 0 值:

public class Foo<T>
{
    private T item;

    public bool IsNullOrDefault()
    {
        return Equals(item, default(T));
    }
}

You could also implement you version of Nullable:您还可以实现您的 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;
    }
}

Example:例子:

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

I ran into this issue for a simpler case of wanting a generic static method that could take anything "nullable" (either reference types or Nullables), which brought me to this question with no satisfactory solution.我遇到了这个问题,因为它需要一个可以采用任何“可空”(引用类型或可空值)的通用静态方法,这让我遇到了这个问题,但没有令人满意的解决方案。 So I came up with my own solution which was relatively easier to solve than the OP's stated question by simply having two overloaded methods, one that takes a T and has the constraint where T : class and another that takes a T?所以我想出了我自己的解决方案,它比 OP 提出的问题更容易解决,只需使用两个重载方法,一个采用T并具有约束where T : class和另一个采用T? and has where T : struct .并且有where T : struct

I then realized, that solution can also be applied to this problem to create a solution that is checkable at compile time by making the constructor private (or protected) and using a static factory method:然后我意识到,该解决方案也可以应用于此问题,通过将构造函数设为私有(或受保护)并使用静态工厂方法来创建可在编译时检查的解决方案:

    //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);
        }
    }

Now we can use it like this:现在我们可以这样使用它:

        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

If you want a parameterless constructor, you won't get the nicety of overloading, but you can still do something like this:如果你想要一个无参数的构造函数,你不会得到重载的好处,但你仍然可以做这样的事情:

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

And use it like this:并像这样使用它:

        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

There are few disadvantages to this solution, one is that you may prefer using 'new' to construct objects.这种解决方案有几个缺点,一个是您可能更喜欢使用“new”来构造对象。 Another is that you won't be able to use Foo<T> as a generic type argument for a type constraint of something like: where TFoo: new() .另一个是您将无法使用Foo<T>作为类型约束的泛型参数,例如: where TFoo: new() Finally is the bit of extra code you need here which would increase especially if you need multiple overloaded constructors.最后是您在此处需要的一些额外代码,特别是如果您需要多个重载构造函数时,这些代码会增加。

As mentioned, you cannot have a compile-time check for it.如前所述,您无法对其进行编译时检查。 Generic constraints in .NET are severely lacking, and do not support most scenarios. .NET 中的通用约束严重缺乏,并且不支持大多数场景。

However I consider this to be a better solution for run-time checking.但是,我认为这是运行时检查的更好解决方案。 It can be optimized at JIT compilation time, since they're both constants.它可以在 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);
    }
}

Such a type constraint is not possible.这种类型约束是不可能的。 According to the documentation of type constraints there is not constraint that captures both the nullable and the reference types.根据 类型约束文档,没有捕获可空类型和引用类型的约束。 Since constraints can only be combined in a conjunction, there is no way to create such a constraint by combination.由于约束只能以连词组合,因此无法通过组合创建这样的约束。

You can, however, for your needs fall back to an unconstraint type parameter, since you can always check for == null.但是,您可以根据需要回退到无约束类型参数,因为您始终可以检查 == null。 If the type is a value type the check will just always evaluate to false.如果类型是值类型,则检查将始终评估为 false。 Then you'll possibly get the R# warning "Possible compare of value type with null", which is not critical, as long as the semantics is right for you.然后您可能会收到 R# 警告“可能将值类型与 null 进行比较”,这并不重要,只要语义适合您。

An alternative could be to use另一种方法是使用

object.Equals(value, default(T))

instead of the null check, since default(T) where T : class is always null.而不是空检查,因为 default(T) 其中 T : class 始终为空。 This, however, means that you cannot distinguish weather a non-nullable value has never been set explicitly or was just set to its default value.但是,这意味着您无法区分从未显式设置或仅设置为其默认值的不可空值的天气。

I use我用

public class Foo<T> where T: struct
{
    private T? item;
}

If you only want to allow nullable value types and reference types, and disallow non-nullable value types, then I think you're out of luck as of C# 9.如果您只想允许可空值类型和引用类型,而不允许不可空值类型,那么我认为您从 C# 9 开始就不走运了。

I am writing a pipes and filters application, and want to use a null reference as the last item that passes into the pipeline, so that every filter can shut down nicely, do cleanup, etc...我正在编写一个管道和过滤器应用程序,并希望使用空引用作为传入管道的最后一项,以便每个过滤器都可以很好地关闭,进行清理等...

In other words, you need to reserve a special value that indicates the end-of-stream.换句话说,您需要保留一个指示流结束的特殊值。

Consider creating a wrapper type that provides this.考虑创建一个提供此功能的包装器类型。 It'd be similar to how Nullable<T> is implemented, and has the additional benefit of allowing a non-end-of-stream null value to be transmitted, should that be useful.它类似于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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM