将泛型限制为可以为空的内容

本文关键字:泛型 | 更新日期: 2023-09-27 17:52:41

我想限制一个通用的我编码到任何可以是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中将其用作参数'T'。MyGeneric '

所以我尝试添加System.Nullable作为T的可接受类型:

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

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

I then try

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

将泛型限制为可以为空的内容

不,没有办法在编译时这样做。

就我个人而言,我只是让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)));
        }
    }
}

不,您不能将泛型限制为只能为null的内容。参见我如何从c#中的泛型方法返回NULL ?举例来说。唯一的解决方案要么使用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");
        }
    }
}

从你的例子中不清楚的一件事是为什么你想用类级泛型而不是方法级泛型来做这件事。根据您的示例,您可以执行以下操作:

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

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

有时候,类型系统就是不能做你想做的事情。在这些情况下,你要么改变你想要的,要么绕开它。

考虑Tuple<>类的例子。它的"最大"版本看起来像Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>,其中TRest必须是Tuple<>。这不是编译时间限制,而是严格的运行时验证。如果您想在Foo<T>中强制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