Corri para esse problema para um caso mais simples de querer um método estático genérico que pudesse tomar qualquer coisa "anulável" (tipos de referência ou Nullables), o que me trouxe a esta questão sem solução satisfatória. Então eu vim com minha própria solução que era relativamente mais fácil de resolver do que a pergunta declarada do OP, simplesmente tendo dois métodos sobrecarregados, um que leva a T
e tem a restrição where T : class
e outro que leva a T?
e tem where T : struct
.
Então percebi que essa solução também pode ser aplicada a este problema para criar uma solução que seja verificável em tempo de compilação, tornando o construtor privado (ou protegido) e usando um método de fábrica estático:
//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);
}
}
Agora podemos usá-lo assim:
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 você quiser um construtor sem parâmetros, não terá a delicadeza da sobrecarga, mas ainda pode fazer algo assim:
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 use-o assim:
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
Existem algumas desvantagens para esta solução, uma é que você pode preferir usar 'novo' para construir objetos. Outra é que você não será capaz de usar Foo<T>
como um argumento de tipo genérico para um tipo de restrição de algo como: where TFoo: new()
. Finalmente, está o código extra de que você precisa aqui, que aumentaria especialmente se você precisar de vários construtores sobrecarregados.