将泛型限制为可以为null的事物

时间:2011-04-20 23:55:01

标签: c# generics null nullable

我想将通用的I编码限制为null的任何内容。这基本上是任何课程+ System.Nullable(例如int?等)。

对于课堂部分,这很容易:

public class MyGeneric<T> where T : class {}

但是,这不允许我这样做:

var myGeneric = new MyGeneric<int?>();

或者这个:

var myGeneric = new MyGeneric<Nullable<int>>();

编译器抱怨:
错误CS0452:类型'int?'必须是引用类型才能在泛型类型或方法'Test.MyGeneric'

中将其用作参数'T'

所以我尝试将addind System.Nullable作为T的接受类型:

public class MyGeneric<T> where T : class, System.Nullable {}

但它不会这样做。编译器返回以下错误:
错误CS0717:'System.Nullable':静态类不能用作约束

然后我尝试了

public class MyGeneric<T> where T : class, INullable {}

它确实编译,但是当我这样做时:

var myGeneric = new MyGeneric<string>();

编译器返回此错误:
错误CS0311:类型'string'不能用作泛型类型或方法'Test.MyGeneric'中的类型参数'T'。没有从'string'到'System.Data.SqlTypes.INullable'的隐式引用转换。

所以,问题是:是否可以将泛型限制为null的任何内容,以及如何限制?

供参考,我使用的是VS2010 / C#4.0

修改
我被问到我想用它做什么。这是一个例子:

namespace Test
{
    public class MyGeneric<T> where T : class
    {
        private IEnumerable<T> Vals { get; set; }

        public MyGeneric(params T[] vals)
        {
            Vals = (IEnumerable<T>)vals;
        }

        public void Print()
        {
            foreach (var v in Vals.Where(v => v != default(T)))
            {
                Trace.Write(v.ToString());
            }
            Trace.WriteLine(string.Empty);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyGeneric<string> foo = 
                new MyGeneric<string>("a", "b", "c", null, null, "g");
            foo.Print();
        }
    }
}

此程序在调试控制台中打印abcg

4 个答案:

答案 0 :(得分:12)

不,无法在编译时执行此操作

就个人而言,我只是让T成为任何东西,然后我会在静态构造函数中检查它的有效性:

public class MyGeneric<T>
{
    static MyGeneric()
    {
        var def = default(T); 
        if (def is ValueType && Nullable.GetUnderlyingType(typeof(T)) == null)
        {
            throw new InvalidOperationException(
                string.Format("Cannot instantiate with non-nullable type: {0}",
                    typeof(T)));
        }
    }
}

答案 1 :(得分:2)

不,你不能将泛型限制为只能为null的东西。例如,请参阅How can I return NULL from a generic method in C#?。提供的唯一解决方案是使用default(T)代替或使用class限制,因为无法限制只有可空类型。

目前尚不清楚你的目的是什么。只更改几行示例代码使其适用于任何类型,而不仅仅是可以为空的类型,因此我不明白为什么要尝试限制它。

此示例也适用于MyGeneric<int?>MyGeneric<int>

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Test
{
    public class MyGeneric<T> // removed "where T : class"
    {
        public void Print(params T[] vals)
        {
            Print((IEnumerable<T>) vals);
        }

        public void Print(IEnumerable<T> vals)
        {
            foreach (var v in vals.OfType<T>()) // use "OfType" instead of "Where"
            {
                Trace.WriteLine(v.ToString());
            }
            Trace.WriteLine(string.Empty);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyGeneric<string> foo = new MyGeneric<string>();
            foo.Print("a", "b", "c", null, null, "g");
        }
    }
}

答案 2 :(得分:1)

您的示例中不清楚的一件事是您希望使用类级别泛型而不是方法级别泛型来执行此操作。根据您的示例,您可以执行以下操作:

public class NonGenericClass
{
    public void Print<T>(IEnumerable<T?> vals) where T : struct
    {
        PrintRestricted(vals.Where(v =>  v.HasValue));
    }

    public void Print<U>(IEnumerable<T> vals) where T : class
    {
        PrintRestricted(vals.Where(v => v != default(T)));
    }

    private void PrintRestricted<U>(IEnumerable<T> vals)
    {
        foreach (var v in vals)
        {
            Trace.WriteLine(v.ToString());
        }
        Trace.WriteLine(string.Empty);
    }
}

对于编写执行限制的包装方法的成本,您可以获得相同的功能。

答案 3 :(得分:0)

有时类型系统根本无法做到你想要的。在这些情况下,您可以更改所需内容,也可以解决它。

考虑Tuple<>类的示例。它的“最大”版本看起来像Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>,其中TRest必须是Tuple<>。这不是编译时限制,它严格来说是运行时验证。如果要在T中强制执行Foo<T>的可空性要求,则可能必须使用类似的东西,同时支持典型类和可空结构。

/// <summary>
/// Class Foo requires T to be type that can be null. E.g., a class or a Nullable&lt;T&gt;
/// </summary>
/// <typeparam name="T"></typeparam>
class Foo<T>
{
    public Foo()
    {
        if (default(T) != null)
            throw new InvalidOperationException(string.Format("Type {0} is not valid", typeof(T)));
    }

    // other members here
}

在执行此操作时,我正在记录该类将要求T与可空类型兼容,如果不是,则抛出构造函数。

Foo<string> foo = new Foo<string>(); // OK
Foo<int?> works = new Foo<int?>(); // also OK
Foo<int> broken = new Foo<int>(); // not OK