Я зіткнувся з цим питанням для більш простого випадку, коли потрібно загальний статичний метод, який міг би прийняти що-небудь "змінне" (або типи посилань, або Nullables), що привело мене до цього питання, не маючи задовільного рішення. Тож я придумав власне рішення, яке було порівняно простіше вирішити, ніж заявлене питання ОП, просто маючи два перевантажені методи: один, який приймає 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()
. Нарешті, тут вам потрібен додатковий код, який особливо збільшиться, якщо вам потрібно кілька перевантажених конструкторів.