2013-11-07 4 views
67

Так у меня есть этот класс:C# общего типа ограничение для всех обнуляемого

public class Foo<T> where T : ??? 
{ 
    private T item; 

    public bool IsNull() 
    { 
     return item == null; 
    } 

} 

Теперь я ищу типа ограничения, что позволяет мне использовать все в качестве параметра типа, который может быть null. Это означает, что все ссылочные типы, а также все Nullable (T?) типов:

Foo<String> ... = ... 
Foo<int?> ... = ... 

должно быть возможно.

Использование class как ограничение типа позволяет мне использовать ссылочные типы.

Дополнительная информация: Я пишу трубы и фильтры приложения, и вы хотите использовать null ссылку в качестве последнего элемента, который проходит в трубопровод, так что каждый фильтр может закрыть красиво, сделать уборку и т.д. ..

+1

@Tim, который не допускает Nullables – Rik

+0

Эта ссылка может помочь вам: http://social.msdn.microsoft.com/Forums/en-US/df6bd378-7ff9-4298-852d-a92deecc77e7/nullable-constraint –

+2

Это невозможно сделать напрямую. Возможно, вы можете рассказать нам больше о вашем сценарии? Или, может быть, вы можете использовать 'IFoo ' как рабочий тип и создавать экземпляры с помощью заводского метода? Это можно заставить работать. – Jon

ответ

11

Я не знаю, как реализовать эквивалент ИЛИ в дженериках. Однако я могу предложить использовать по умолчанию ключевого слова для того, чтобы создать нуль для обнуляемых типов и значения 0 для структур:

public class Foo<T> 
{ 
    private T item; 

    public bool IsNullOrDefault() 
    { 
     return Equals(item, default(T)); 
    } 
} 

Вы также мог бы реализовать вам версию обнуляемый:

class MyNullable<T> where T : struct 
{ 
    public T Value { get; set; } 

    public static implicit operator T(MyNullable<T> value) 
    { 
     return value != null ? value.Value : default(T); 
    } 

    public static implicit operator MyNullable<T>(T value) 
    { 
     return new MyNullable<T> { Value = value }; 
    } 
} 

class Foo<T> where T : class 
{ 
    public T Item { get; set; } 

    public bool IsNull() 
    { 
     return Item == null; 
    } 
} 

Примера:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Console.WriteLine(new Foo<MyNullable<int>>().IsNull()); // true 
     Console.WriteLine(new Foo<MyNullable<int>> {Item = 3}.IsNull()); // false 
     Console.WriteLine(new Foo<object>().IsNull()); // true 
     Console.WriteLine(new Foo<object> {Item = new object()}.IsNull()); // false 

     var foo5 = new Foo<MyNullable<int>>(); 
     int integer = foo5.Item; 
     Console.WriteLine(integer); // 0 

     var foo6 = new Foo<MyNullable<double>>(); 
     double real = foo6.Item; 
     Console.WriteLine(real); // 0 

     var foo7 = new Foo<MyNullable<double>>(); 
     foo7.Item = null; 
     Console.WriteLine(foo7.Item); // 0 
     Console.WriteLine(foo7.IsNull()); // true 
     foo7.Item = 3.5; 
     Console.WriteLine(foo7.Item); // 3.5 
     Console.WriteLine(foo7.IsNull()); // false 

     // var foo5 = new Foo<int>(); // Not compile 
    } 
} 
+0

Исходный Nullable в структуре - это структура, а не класс. Я не думаю, что это хорошая идея создать оболочку ссылочного типа, которая будет имитировать тип значения. –

+1

Первое предложение с использованием ** по умолчанию ** идеально! Теперь мой шаблон с возвращаемым общим типом может возвращать значение null для объектов и значение по умолчанию для встроенных типов. –

3

Такое ограничение типа невозможно. В соответствии с documentation of type constraints не существует ограничений, которые фиксируют как допустимые, так и ссылочные типы. Поскольку ограничения можно комбинировать только в конъюнкции, невозможно создать такое ограничение по комбинации.

Вы можете, однако, для своих нужд вернуться к параметру типа unconstraint, поскольку вы всегда можете проверить на == null. Если тип является типом значения, проверка всегда будет оцениваться как false. Тогда вы, возможно, получите предупреждение R # «Возможное сравнение типа значения с нулевым», что не является критическим, если семантика подходит вам.

Альтернативой может быть использование

object.Equals(value, default(T)) 

вместо нулевой проверки, так как по умолчанию (T), где T: класс всегда нуль. Это, однако, означает, что вы не можете отличить погоду от значения, которое не имеет значения NULL, никогда не устанавливалось явно или просто было установлено значение по умолчанию.

+0

Я думаю, что проблема в том, как проверить, что значение никогда не задано. Кажется, что отличное от нуля значение указывает на то, что значение было инициализировано. –

+0

Это не делает недействительным подход, так как типы значений всегда задаются (по крайней мере, неявно с их соответствующим значением по умолчанию). –

-2
public class Foo<T> 
    { 
     private T item; 

     public Foo(T item) 
     { 
      this.item = item; 
     } 

     public bool IsNull() 
     { 
      return object.Equals(item, null); 
     } 
    } 

    var fooStruct = new Foo<int?>(3); 
     var b = fooStruct.IsNull(); 

     var fooStruct1 = new Foo<int>(3); 
     b = fooStruct1.IsNull(); 

     var fooStruct2 = new Foo<int?>(null); 
     b = fooStruct2.IsNull(); 

     var fooStruct3 = new Foo<string>("qqq"); 
     b = fooStruct3.IsNull(); 

     var fooStruct4 = new Foo<string>(null); 
     b = fooStruct4.IsNull(); 
+0

Этот ввод позволяет new Foo (42), а IsNull() возвращает false, что, в то время как семантически корректно, не имеет особого смысла. –

+0

42 не является нулевым, все правильно. – SeeSharp

+1

42 «Ответ на конечный вопрос жизни, Вселенной и всего». Проще говоря: IsNull для каждого значения int вернет false (даже для значения 0). –

17

Если вы готовы сделать проверку выполнения в конструктор Foo, а не имея чек во время компиляции, вы можете проверить, если тип не является ссылкой или обнуляемым типа, и бросить исключение, если это так ,

Я понимаю, что только при наличии проверки во время выполнения может быть неприемлемо, но только в том случае:

public class Foo<T> 
{ 
    private T item; 

    public Foo() 
    { 
     var type = typeof(T); 

     if (Nullable.GetUnderlyingType(type) != null) 
      return; 

     if (type.IsClass) 
      return; 

     throw new InvalidOperationException("Type is not nullable or reference type."); 
    } 

    public bool IsNull() 
    { 
     return item == null; 
    } 
} 

Затем следующий код компилируется, но последний (foo3) бросает исключение в конструкторе:

var foo1 = new Foo<int?>(); 
Console.WriteLine(foo1.IsNull()); 

var foo2 = new Foo<string>(); 
Console.WriteLine(foo2.IsNull()); 

var foo3= new Foo<int>(); // THROWS 
Console.WriteLine(foo3.IsNull()); 
+20

Если вы сделаете это, убедитесь, что вы выполняете проверку в * статическом * конструкторе, иначе вы будете замедлять построение каждого экземпляра вашего общего класса (необязательно) –

+0

@EamonNerbonne Вы не должны создавать исключения от статических конструкторов: https://msdn.microsoft.com/en-us/library/bb386039.aspx –

+2

Рекомендации не являются абсолютными. Если вам нужна эта проверка, вам придется компрометировать стоимость проверки времени выполнения и нежелательности исключений в статическом конструкторе. Поскольку вы действительно внедряете статический анализатор бедного человека, это исключение никогда не следует бросать, кроме как во время разработки. Наконец, даже если вы хотите избежать исключений из статической конструкции любой ценой (неразумно), вам следует сделать как можно больше работы статически и как можно меньше в конструкторе экземпляра - например, установив флаг «isBorked» или что-то еще. –

3

Как уже упоминалось, вы не можете выполнить проверку времени компиляции. Общие ограничения в .NET сильно отсутствуют и не поддерживают большинство сценариев.

Однако я считаю это лучшим решением для проверки времени выполнения. Его можно оптимизировать во время компиляции JIT, так как они оба являются константами.

public class SomeClass<T> 
{ 
    public SomeClass() 
    { 
     // JIT-compile time check, so it doesn't even have to evaluate. 
     if (default(T) != null) 
      throw new InvalidOperationException("SomeClass<T> requires T to be a nullable type."); 

     T variable; 
     // This still won't compile 
     // variable = null; 
     // but because you know it's a nullable type, this works just fine 
     variable = default(T); 
    } 
} 
2

Я использую

public class Foo<T> where T: struct 
{ 
    private T? item; 
} 
3

Я столкнулся с этим вопросом, для более простого случая желания родового статического метода, который мог бы взять что-нибудь «обнуляемый» (либо ссылки на типы или Nullables), который привел меня этот вопрос без удовлетворительного решения. Поэтому я придумал свое решение, которое было относительно легче решить, чем заявленный вопрос OP, просто имея два перегруженных метода, один из которых принимает T и имеет ограничение where T : class, а другое - T? и имеет where T : struct.

Я тогда понял, что решение может быть применен к этой проблеме, чтобы создать решение, которое проверяемое во время компиляции, что делает конструктор частным (или защищенным) и с использованием статического фабричного метода:

//this class is to avoid having to supply generic type arguments 
    //to the static factory call (see CA1000) 
    public static class Foo 
    { 
     public static Foo<TFoo> Create<TFoo>(TFoo value) 
      where TFoo : class 
     { 
      return Foo<TFoo>.Create(value); 
     } 

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

    public class Foo<T> 
    { 
     private T item; 

     private Foo(T value) 
     { 
      item = value; 
     } 

     public bool IsNull() 
     { 
      return item == null; 
     } 

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

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

Теперь мы можем использовать его как это:

 var foo1 = new Foo<int>(1); //does not compile 
     var foo2 = Foo.Create(2); //does not compile 
     var foo3 = Foo.Create(""); //compiles 
     var foo4 = Foo.Create(new object()); //compiles 
     var foo5 = Foo.Create((int?)5); //compiles 

Если вы хотите, конструктор без параметров, вы не получите Тонкость перегрузки, но вы можете сделать что-то вроде этого:

public static class Foo 
    { 
     public static Foo<TFoo> Create<TFoo>() 
      where TFoo : class 
     { 
      return Foo<TFoo>.Create<TFoo>(); 
     } 

     public static Foo<TFoo?> CreateNullable<TFoo>() 
      where TFoo : struct 
     { 
      return Foo<TFoo?>.CreateNullable<TFoo>(); 
     } 
    } 

    public class Foo<T> 
    { 
     private T item; 

     private Foo() 
     { 
     } 

     public bool IsNull() 
     { 
      return item == null; 
     } 

     internal static Foo<TFoo> Create<TFoo>() 
      where TFoo : class 
     { 
      return new Foo<TFoo>(); 
     } 

     internal static Foo<TFoo?> CreateNullable<TFoo>() 
      where TFoo : struct 
     { 
      return new Foo<TFoo?>(); 
     } 
    } 

И использовать его как это:

 var foo1 = new Foo<int>(); //does not compile 
     var foo2 = Foo.Create<int>(); //does not compile 
     var foo3 = Foo.Create<string>(); //compiles 
     var foo4 = Foo.Create<object>(); //compiles 
     var foo5 = Foo.CreateNullable<int>(); //compiles 

Есть несколько недостатков для этого решения, является то, что вы можете предпочесть использовать «новый» для создания объектов. Другим является то, что вы не сможете использовать Foo<T> в качестве аргумента общего типа для ограничения типа чего-то вроде: where TFoo: new(). Наконец, это бит дополнительного кода, который вам нужен здесь, который бы увеличился, особенно если вам нужно несколько перегруженных конструкторов.

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