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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19831157/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 15:57:17  来源:igfitidea点击:

C# generic type constraint for everything nullable

c#genericsnullable

提问by jkammerer

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. That means all reference types, as well as all the Nullable(T?) types:

现在我正在寻找一种类型约束,它允许我将所有内容用作可以是null. 这意味着所有引用类型,以及所有Nullable( T?) 类型:

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

should be possible.

应该是可能的。

Using classas 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 nullreference as the last item that passes into the pipeline, so that every filter can shut down nicely, do cleanup, etc...

附加信息:我正在编写一个管道和过滤器应用程序,并希望使用一个null引用作为传入管道的最后一项,以便每个过滤器都可以很好地关闭,进行清理等...

回答by Ryszard D?egan

I don't know how to implement equivalent to ORin generics. However I can propose to use defaultkey word in order to create null for nullable types and 0 value for structures:

我不知道如何在泛型中实现等价于OR。但是,我可以建议使用默认关键字,以便为可空类型创建 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
    }
}

回答by Sven Amann

Such a type constraint is not possible. According to the documentation of type constraintsthere 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. If the type is a value type the check will just always evaluate to 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.

但是,您可以根据需要回退到无约束类型参数,因为您始终可以检查 == null。如果类型是值类型,则检查将始终评估为 false。然后您可能会收到 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. 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.

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

回答by SeeSharp

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

回答by Matthew Watson

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

回答by Aidiakapi

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

回答by ela

I use

我用

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

回答by Dave M

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 Tand has the constraint where T : classand another that takes a T?and has where T : struct.

我遇到了这个问题,因为它需要一个可以采用任何“可空”(引用类型或可空值)的通用静态方法,这让我遇到了这个问题,但没有令人满意的解决方案。所以我想出了我自己的解决方案,它比 OP 提出的问题更容易解决,只需使用两个重载方法,一个采用 aT并具有约束where T : class,另一个采用 aT?并具有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. 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(). Finally is the bit of extra code you need here which would increase especially if you need multiple overloaded constructors.

这种解决方案有几个缺点,一个是您可能更喜欢使用“new”来构造对象。另一个原因是,你将无法使用Foo<T>作为的类似类型约束泛型类型参数:where TFoo: new()。最后是您在此处需要的一些额外代码,特别是如果您需要多个重载构造函数时,这些代码会增加。