Подтвердить что ты не робот

Ограничить общий тип как nullable

Я ищу пример использования примерно так:

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 в значение NULL:

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 типами, поддерживающими nullability, то это было бы хорошо.

Итак, после всего этого, есть ли способ ограничить T типом NULL?

Некоторые дополнительные примечания:.NET 4.0, VS2010. И я нашел здесь один подобный вопрос, но без успешного ответа.

4b9b3361

Ответ 1

Возможно, вы можете сделать конструктор Foo<T> внутренним и потребовать, чтобы новые экземпляры могли быть созданы только с помощью класса factory:

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

Нет ограничений, которые вы можете применить для этого, но вы можете проверить его во время выполнения:

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

Вы могли бы даже поместить это в статический конструктор, который бы удостоверился, что он выполняется только один раз для каждого сконструированного типа - и каждый, кто пытается использовать Foo<int> в любом месте, будет с трудом игнорировать TypeInitializerException. Это не очень дружелюбно для публичного API, но я считаю это разумным для внутреннего.

EDIT: существует один ужасный способ создать экземпляры Foo<int>... вы можете использовать ghastly code в этом сообщении блога (с использованием правил разрешения перегрузки вместе с параметрами по умолчанию и с несколькими ограниченными типичными типами) и отметьте перегрузку, которая направлена ​​на тип с нулевым значением, который устарел с ошибкой. Таким образом, Foo<int> по-прежнему будет действительным типом, но вам будет сложно создать экземпляр его. Я не буду рекомендовать вам это делать, хотя...

Ответ 3

Мне не нравится это как синтаксис 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);

Это все еще подвержено ошибкам, потому что получение свойства Value Foo3 (и Foo2) не является простым. Foo1 был лучшим, потому что автоматически завернутое значение будет иметь нулевую поддержку.

Мне может понадобиться ValueTypeFoo и ObjectFoo и иметь дело с двумя версиями.