Mi sono imbattuto in questo problema per un caso più semplice di volere un metodo statico generico che potesse accettare qualsiasi cosa "nullable" (tipi di riferimento o Nullables), il che mi ha portato a questa domanda senza una soluzione soddisfacente. Quindi ho trovato la mia soluzione che era relativamente più facile da risolvere rispetto alla domanda dichiarata dell'OP semplicemente avendo due metodi sovraccaricati, uno che accetta a T
e ha il vincolo where T : class
e un altro che richiede a T?
e ha where T : struct
.
Mi sono quindi reso conto che quella soluzione può essere applicata anche a questo problema per creare una soluzione controllabile in fase di compilazione rendendo il costruttore privato (o protetto) e utilizzando un metodo factory statico:
//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);
}
}
Ora possiamo usarlo in questo modo:
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
Se vuoi un costruttore senza parametri, non otterrai la delicatezza del sovraccarico, ma puoi comunque fare qualcosa del genere:
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?>();
}
}
E usalo in questo modo:
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
Ci sono pochi svantaggi in questa soluzione, uno è che potresti preferire l'utilizzo di "nuovo" per costruire oggetti. Un altro è che non sarà in grado di utilizzare Foo<T>
come un tipo di argomento generico per un vincolo tipo di qualcosa come: where TFoo: new()
. Infine è il pezzo di codice extra di cui hai bisogno qui che aumenterebbe soprattutto se hai bisogno di più costruttori sovraccarichi.