2013-02-15 1 views
14

Я ищу использование образца что-то вроде этого:Constrain родовых быть обнуляемым Типу

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, я могу автоматический перенос т в обнуляемый:

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. И я нашел здесь один подобный вопрос, но без успешного ответа.

ответ

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

Интересно. В реальной жизни Foo более сложный и автоматически создается каркасом. Мне нужно посмотреть, есть ли способ подключить/настроить способ создания объектов, чтобы сделать этот подход выполнимым. – tcarvin

+0

Принято, так как это дает наименьший сюрприз внутреннему клиенту библиотеки (время разработки/компиляции и время выполнения). Но тест, использованный @JonSkeet, тоже был отличным! – tcarvin

17

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

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

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

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

+0

@NikolayKhil: комментарий, на который вы отвечаете, исчез, но код в этом ответе * будет * работать для 'int?', Потому что проверка недействительности «делает правильную вещь» для аргументов с нулевым типом. –

+0

Я не прав или нулевых типов - все структуры? Я имею в виду, что нет ограничений для «нулевых типов», но для структуры существует ограничение. Но я сделал свой собственный тест, и если, если вы укажете ограничение T: struct на какой-то общий параметр, позже вы не сможете использовать тип с нулевым именем в качестве общего аргумента. Например, X не будет работать с этим ограничением. –

+0

Очевидно, это потому, что int? является Nullable и Nullable является ссылочным типом, но, возможно, .NET devteam может сделать какой-то синтаксический сахар, чтобы разрешить этот случай использования. –

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

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

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

Смежные вопросы