132

所以我有这个类:

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

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

}

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

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

应该是可能的。

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

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

4

8 回答 8

25

如果您愿意在 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());
于 2013-11-07T09:02:02.027 回答
23

我不知道如何在泛型中实现等价于OR 。但是,我可以建议使用默认关键字来为可空类型创建 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
    }
}
于 2013-11-07T08:55:38.777 回答
19

我遇到了这个问题,因为需要一个可以采用任何“可为空”(引用类型或 Nullables)的通用静态方法的更简单的情况,这使我遇到了这个问题,但没有令人满意的解决方案。因此,我提出了自己的解决方案,该解决方案比 OP 提出的问题更容易解决,只需使用两个重载方法,一个采用 aT并具有约束where T : class,另一个采用 aT?并具有约束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

此解决方案几乎没有缺点,一个是您可能更喜欢使用“新”来构造对象。另一个是您将无法将其Foo<T>用作类型约束的泛型类型参数,例如:where TFoo: new()。最后是您需要的一些额外代码,尤其是在您需要多个重载构造函数时。

于 2017-04-11T15:38:04.523 回答
9

如前所述,您不能对其进行编译时检查。.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);
    }
}
于 2015-03-14T16:27:35.900 回答
3

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

但是,您可以根据需要回退到无约束类型参数,因为您始终可以检查 == null。如果类型是值类型,则检查将始终评估为假。然后,您可能会收到 R# 警告“可能将值类型与 null 进行比较”,这并不重要,只要语义适合您。

另一种方法是使用

object.Equals(value, default(T))

而不是空检查,因为 default(T) where T : class 始终为空。但是,这意味着您无法区分一个不可为空的值从未被明确设置或只是设置为其默认值的天气。

于 2013-11-07T08:56:22.233 回答
3

我用

public class Foo<T> where T: struct
{
    private T? item;
}
于 2016-03-16T10:04:57.367 回答
1

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

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

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

考虑创建一个提供此功能的包装器类型。它与Nullable<T>实现方式类似,并且具有允许传输非流结束null值的额外好处,如果这有用的话。

public readonly struct StreamValue<T>
{
    public bool IsEndOfStream { get; }
    public T Value { get; }
}
于 2020-09-17T07:56:49.213 回答
-2
    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();
于 2013-11-07T09:01:02.570 回答