将泛型约束为可空类型

时间:2013-02-15 13:20:13

标签: c# .net generics

我正在寻找类似这样的示例用法:

Foo<string> stringFoo = new Foo<string>("The answer is");
Foo<int> intFoo = new Foo<int>(42);
// The Value of intFoo & stringFoo are strongly typed
stringFoo.Nullify();
intFoo.Nullify();
if (stringFoo == null && intFoo == null)
   MessageBox.Show("Both are null);

鉴于这个类Foo,我可以将T自动换行为可空:

public class Foo1<T>
   where T : struct
{
   private T? _value;

   public Foo(T? initValue)
   {
      _value = initValue;
   }

   public T? Value { get { return _value; } }

   public void Nullify { _value = null; }

}

这适用于基元,但不适用于String或其他类。

Next flavor适用于字符串,但不适用于原语:

public class Foo2<T>
{
   private T _value;

   public Foo(T initValue)
   {
      _value = initValue;
   }

   public T Value { get { return _value; } }

   public void Nullify { _value = default(T); }

}

我可以将Nullable<int>用于Foo2,代码可以这样工作:

Foo2<int?> intFoo = new Foo<int?>(42);

但这很容易出错,因为Foo2失败了。如果我可以将T约束为支持可空性的类型那么那就没问题了。

所以,毕竟有什么方法可以将T限制为可以为空的类型吗?

一些附加说明:.NET 4.0,VS2010。我确实在这里找到了一个类似的问题,但没有成功的答案。

3 个答案:

答案 0 :(得分:18)

您可以对此进行约束,但可以在执行时对其进行测试:

if (default(T) != null)
{
    throw new SomeAppropriateException(typeof(T) + " is not a nullable type");
}

你甚至可以将它放入一个静态构造函数中,这样可以确保它只对每个构造的类型执行一次 - 任何试图在任何地方使用Foo<int>的人都会很难忽略TypeInitializerException。对于 public API来说,这并不是非常友好,但我认为这对内部API来说是合理的。

编辑:有一种可怕的方式使得创建Foo<int>的实例变得更难......你可以使用ghastly code in this blog post(使用重载决策规则和默认值参数和一些受约束的泛型类型)并将针对非可空值类型的重载标记为过时的错误。这样,Foo<int>仍然是一个有效的类型,但你很难创建它的实例。我不打算建议你这样做......

答案 1 :(得分:9)

您可以将Foo<T>内部的构造函数设置为内部,并要求只能通过工厂类创建新实例:

public class Foo<T>
{
    private T value;
    internal Foo(T value)
    {
        this.value = value;
    }

    public void Nullify()
    {
        this.value = default(T);
    }

    public T Value { get { return this.value; } }
}

public class Foo
{
    public static Foo<T> Create<T>(T value) where T : class
    {
        return new Foo<T>(value);
    }

    public static Foo<T?> Create<T>(T? value) where T : struct
    {
        return new Foo<T?>(value);
    }
}

答案 2 :(得分:0)

我不喜欢它和Foo1的语法一样,但这里是Foo3:

public class Foo3<T>
   where T : struct
{

   private T _value;
   private T _nullValue;

   public Foo3(T initValue)
       : this(initValue, default(T))
   {
   }

   public Foo3(T initValue, T nullValue)
   {
      _value = initValue;
      _nullValue = nullValue;
   }

   public T Value { get { return _value; } }

    public bool IsNull
    {
        get 
        {
           return object.Equals(_value, _nullValue);
        }
    }

    public void Nullify() { _value = _nullValue; }

}

然后我的用法变为:

Foo3<string> stringFoo = new Foo<string>("The answer is");
Foo3<int> intFoo = new Foo<int>(42, int.MinValue);
stringFoo.Nullify();
intFoo.Nullify();
if (stringFoo.IsNull && intFoo.IsNull)
   MessageBox.Show("Both are null);

这仍然容易出错,因为获取Foo3(和Foo2)的Value属性并不简单。 Foo1是最好的,因为自动包装Value将为null支持。

我可能只需要ValueTypeFoo和ObjectFoo并处理两个版本。