Natknąłem się na ten problem w prostszym przypadku, gdy potrzebowałem ogólnej metody statycznej, która mogłaby przyjmować wszystko, co „dopuszcza wartość zerową” (typy referencyjne lub wartości Nullables), co doprowadziło mnie do tego pytania bez zadowalającego rozwiązania. Tak więc wymyśliłem własne rozwiązanie, które było stosunkowo łatwiejsze do rozwiązania niż zadane pytanie PO, po prostu mając dwie przeciążone metody, jedną, która przyjmuje a T
i ma ograniczenie, where T : class
a drugą, która przyjmuje a T?
i ma where T : struct
.
Wtedy zdałem sobie sprawę, że rozwiązanie można również zastosować do tego problemu, aby stworzyć rozwiązanie, które można sprawdzić w czasie kompilacji, ustawiając konstruktor jako prywatny (lub chroniony) i używając statycznej metody fabryki:
//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);
}
}
Teraz możemy to wykorzystać w ten sposób:
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
Jeśli potrzebujesz konstruktora bez parametrów, nie uzyskasz subtelności przeciążenia, ale nadal możesz zrobić coś takiego:
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?>();
}
}
I użyj tego w ten sposób:
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
To rozwiązanie ma kilka wad, jedną z nich jest to, że możesz preferować używanie „nowego” do konstruowania obiektów. Innym jest to, że nie będą mogli używać Foo<T>
jako argumentu typu rodzajowego dla typu przymusu czegoś podobnego: where TFoo: new()
. Na koniec jest trochę dodatkowego kodu, którego potrzebujesz tutaj, który zwiększyłby się, szczególnie jeśli potrzebujesz wielu przeciążonych konstruktorów.