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

use*_*836 102 c# generics nullable

所以我有这门课:

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

Foo<String> ... = ...
Foo<int?> ... = ...
Run Code Online (Sandbox Code Playgroud)

应该是可能的.

使用class类型约束只允许我使用引用类型.

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

Mat*_*son 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后编译以下代码,但最后一个(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());
Run Code Online (Sandbox Code Playgroud)

  • 如果你要这样做,请确保你在*static*构造函数中进行检查,否则你将减慢你的泛型类的每个实例的构造(不必要) (30认同)
  • 指南不是绝对的.如果要进行此检查,则必须权衡运行时检查的成本与静态构造函数中异常的不可操作性.由于你真的在这里实现了一个穷人静态分析器,除非在开发过程中,否则不应该抛出这个异常.最后,即使你想不惜一切代价避免静态构造异常(不明智),你仍然应该在实例构造函数中尽可能静态地做尽可能多的工作 - 例如通过设置标志"isBorked"或其他什么. (4认同)
  • @EamonNerbonne 你不应该从静态构造函数中引发异常:https://msdn.microsoft.com/en-us/library/bb386039.aspx (2认同)

Rys*_*gan 18

我不知道如何在泛型中实现等效的OR.但是我可以建议使用默认关键字,以便为可空类型创建null,为结构创建0值:

public class Foo<T>
{
    private T item;

    public bool IsNullOrDefault()
    {
        return Equals(item, default(T));
    }
}
Run Code Online (Sandbox Code Playgroud)

你也可以实现你的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

例:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @CaseyAnderson除非默认值有意义,在很多情况下都是有意义的。 (2认同)

Dav*_*e M 11

我遇到了一个简单的例子,想要一个通用的静态方法,它可以采取任何"可空的"(引用类型或Nullables),这让我对这个问题没有满意的解决方案.所以我想出了我自己的解决方案,它比OP的陈述问题更容易解决,只需要两个重载方法,一个采用a T并且有约束where T : class,另一个采用a T?和has 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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在我们可以像这样使用它:

        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
Run Code Online (Sandbox Code Playgroud)

如果你想要一个无参数的构造函数,你将无法获得重载的精确性,但你仍然可以这样做:

    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?>();
        }
    }
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

        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
Run Code Online (Sandbox Code Playgroud)

这个解决方案有一些缺点,一个是您可能更喜欢使用'new'来构造对象.另一个是你不能Foo<T>用作类型约束的泛型类型参数:where TFoo: new().最后是你需要的额外代码,如果你需要多个重载的构造函数,这将会增加.


Aid*_*api 7

如前所述,您无法对其进行编译时检查..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);
    }
}
Run Code Online (Sandbox Code Playgroud)