16

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

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 或其他类。

下一个风味适用于字符串,但不适用于原语:

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。我确实在这里找到了一个与此类似的问题,但没有成功的答案。

4

3 回答 3

20

你可以申请这个没有任何限制,但你可以在执行时测试它:

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

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

编辑:有一种可怕的方法使创建实例变得更加困难Foo<int>......您可以使用这篇博客文章中的可怕代码(使用重载解析规则以及默认参数和几个受约束的泛型类型)并标记重载旨在将不可为空的值类型视为已过时并出现错误。这样,Foo<int>仍然是一个有效的类型,但你很难创建它的实例。不过我不建议你这样做......

于 2013-02-15T13:27:19.887 回答
11

您也许可以制作Foo<T>internal 的构造函数,并要求只能通过工厂类创建新实例:

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);
    }
}
于 2013-02-15T13:43:12.543 回答
-1

我不像 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 并处理两个版本。

于 2013-02-15T14:14:15.503 回答