Ограничение общего на вещи, которые могут быть нулевыми
Я хотел бы ограничить общий код, который я кодирую, для всего, что может быть null
. Это в основном любой класс + System.Nullable
(например, int?
и т.д.).
Для части класса это довольно просто:
public class MyGeneric<T> where T : class {}
Но тогда это не позволяет мне это сделать:
var myGeneric = new MyGeneric<int?>();
или это:
var myGeneric = new MyGeneric<Nullable<int>>();
Компилятор жалуется:
ошибка CS0452: тип 'int?' должен быть ссылочным типом, чтобы использовать его в качестве параметра "T" в родовом типе или методе "Test.MyGeneric"
Итак, я попробовал addind System.Nullable
как принятые типы для T
:
public class MyGeneric<T> where T : class, System.Nullable {}
Но этого не будет. Компилятор возвращает следующую ошибку:
ошибка CS0717: "System.Nullable": статические классы не могут использоваться в качестве ограничений
Затем я попробовал
public class MyGeneric<T> where T : class, INullable {}
Он компилируется, но затем, когда я делаю:
var myGeneric = new MyGeneric<string>();
Компилятор возвращает эту ошибку:
error CS0311: Тип "string" не может использоваться как параметр типа "T" в родовом типе или методе "Test.MyGeneric". Нет никакого неявного преобразования ссылок из 'string' в 'System.Data.SqlTypes.INullable'.
Итак, вопрос в том, можно ли вообще ограничить родовое для всего, что может быть null
, и так, как?
Для справки, я использую VS2010/С# 4.0
изменить
Меня спросили, что я хочу с этим делать. Вот пример:
namespace Test
{
public class MyGeneric<T> where T : class
{
private IEnumerable<T> Vals { get; set; }
public MyGeneric(params T[] vals)
{
Vals = (IEnumerable<T>)vals;
}
public void Print()
{
foreach (var v in Vals.Where(v => v != default(T)))
{
Trace.Write(v.ToString());
}
Trace.WriteLine(string.Empty);
}
}
class Program
{
static void Main(string[] args)
{
MyGeneric<string> foo =
new MyGeneric<string>("a", "b", "c", null, null, "g");
foo.Print();
}
}
}
Эта программа печатает abcg
в консоли отладки.
Ответы
Ответ 1
Нет, нет способа сделать это во время компиляции.
Лично я просто хотел бы T
быть любым, тогда я бы проверял его достоверность в статическом конструкторе:
public class MyGeneric<T>
{
static MyGeneric()
{
var def = default(T);
if (def is ValueType && Nullable.GetUnderlyingType(typeof(T)) == null)
{
throw new InvalidOperationException(
string.Format("Cannot instantiate with non-nullable type: {0}",
typeof(T)));
}
}
}
Ответ 2
Нет, вы не можете ограничить общий доступ только тем, что может быть нулевым. См. Как я могу вернуть NULL из общего метода в С#?. Единственными представленными решениями были либо использование default(T)
, либо использование ограничения class
, потому что нет возможности ограничить только типы с возможностью NULL.
Не понятно, какова ваша цель. Изменение только пары строк вашего примера кода делает работу с любым типом, а не только с нулевыми значениями, поэтому я не понимаю, почему вы пытаетесь его ограничить.
В этом примере также будут работать с MyGeneric<int?>
или MyGeneric<int>
:
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Test
{
public class MyGeneric<T> // removed "where T : class"
{
public void Print(params T[] vals)
{
Print((IEnumerable<T>) vals);
}
public void Print(IEnumerable<T> vals)
{
foreach (var v in vals.OfType<T>()) // use "OfType" instead of "Where"
{
Trace.WriteLine(v.ToString());
}
Trace.WriteLine(string.Empty);
}
}
class Program
{
static void Main(string[] args)
{
MyGeneric<string> foo = new MyGeneric<string>();
foo.Print("a", "b", "c", null, null, "g");
}
}
}
Ответ 3
Единственное, что непонятно из вашего примера, - это то, почему вы хотите сделать это с помощью родового уровня класса, а не общего уровня метода. На основе вашего примера вы можете сделать следующее:
public class NonGenericClass
{
public void Print<T>(IEnumerable<T?> vals) where T : struct
{
PrintRestricted(vals.Where(v => v.HasValue));
}
public void Print<U>(IEnumerable<T> vals) where T : class
{
PrintRestricted(vals.Where(v => v != default(T)));
}
private void PrintRestricted<U>(IEnumerable<T> vals)
{
foreach (var v in vals)
{
Trace.WriteLine(v.ToString());
}
Trace.WriteLine(string.Empty);
}
}
За счет написания метода упаковки, который делает ограничение, вы можете получить ту же функциональность.
Ответ 4
Иногда система типа просто не может делать то, что вы хотите. В этих случаях вы либо меняете то, что хотите, либо работаете вокруг.
Рассмотрим пример класса Tuple<>
. "Самый большой" вариант выглядит как Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>
, где TRest
должен быть Tuple<>
. Это не ограничение времени компиляции, это строго проверка времени выполнения. Возможно, вам придётся прибегнуть к чему-то подобному, если вы хотите применить требование nullability для T
в Foo<T>
, поддерживая как типичные классы, так и nullable structs.
/// <summary>
/// Class Foo requires T to be type that can be null. E.g., a class or a Nullable<T>
/// </summary>
/// <typeparam name="T"></typeparam>
class Foo<T>
{
public Foo()
{
if (default(T) != null)
throw new InvalidOperationException(string.Format("Type {0} is not valid", typeof(T)));
}
// other members here
}
При этом я документирую, что класс будет требовать, чтобы T была совместима с типами NULL и выбрала конструктор, если это не так.
Foo<string> foo = new Foo<string>(); // OK
Foo<int?> works = new Foo<int?>(); // also OK
Foo<int> broken = new Foo<int>(); // not OK