Por que o C# não oferece suporte a tipos genéricos implícitos em construtores de classes?
Pergunta
C# não exige que você especifique um parâmetro de tipo genérico se o compilador puder inferi-lo, por exemplo:
List<int> myInts = new List<int> {0,1,1,
2,3,5,8,13,21,34,55,89,144,233,377,
610,987,1597,2584,4181,6765};
//this statement is clunky
List<string> myStrings = myInts.
Select<int,string>( i => i.ToString() ).
ToList<string>();
//the type is inferred from the lambda expression
//the compiler knows that it's taking an int and
//returning a string
List<string> myStrings = myInts.
Select( i => i.ToString() ).
ToList();
Isso é necessário para tipos anônimos onde você não sabe qual seria o parâmetro de tipo (no intellisense ele aparece como 'a
) porque é adicionado pelo compilador.
Os parâmetros de tipo no nível da classe não permitem fazer isso:
//sample generic class
public class GenericDemo<T>
{
public GenericDemo ( T value )
{
GenericTypedProperty = value;
}
public T GenericTypedProperty {get; set;}
}
//why can't I do:
int anIntValue = 4181;
var item = new GenericDemo( anIntValue ); //type inference fails
//however I can create a wrapper like this:
public static GenericDemo<T> Create<T> ( T value )
{
return new GenericDemo<T> ( value );
}
//then this works - type inference on the method compiles
var item = Create( anIntValue );
Por que o C# não oferece suporte a essa inferência de tipo genérico em nível de classe?
Solução
Na verdade, sua pergunta não é ruim.Tenho brincado com uma linguagem de programação genérica nos últimos anos e, embora nunca tenha conseguido desenvolvê-la (e provavelmente nunca o farei), pensei muito sobre inferência de tipos genéricos e uma das minhas principais prioridades tem sempre foi permitir a construção de classes sem a necessidade de especificar o tipo genérico.
C# simplesmente não possui o conjunto de regras para tornar isso possível.Acho que os desenvolvedores nunca viram a necessidade de incluir isso.Na verdade, o código a seguir estaria muito próximo da sua proposta e resolveria o problema.Tudo o que o C# precisa é de um suporte de sintaxe adicional.
class Foo<T> {
public Foo(T x) { … }
}
// Notice: non-generic class overload. Possible in C#!
class Foo {
public static Foo<T> ctor<T>(T x) { return new Foo<T>(x); }
}
var x = Foo.ctor(42);
Como esse código realmente funciona, mostramos que o problema não é de semântica, mas simplesmente de falta de suporte.Acho que tenho que retirar minha postagem anterior.;-)
Outras dicas
Por que o C# não oferece suporte a essa inferência de tipo genérico em nível de classe?
Porque geralmente são ambíguos.Por outro lado, a inferência de tipos é trivial para chamadas de função (se todos os tipos aparecerem nos argumentos).Mas no caso de chamadas de construtor (funções glorificadas, para fins de discussão), o compilador precisa resolver vários níveis ao mesmo tempo.Um nível é o nível da classe e o outro é o nível dos argumentos do construtor.Acredito que resolver isso não é algoritmicamente trivial.Intuitivamente, eu diria que é até NP-completo.
Para ilustrar um caso extremo onde a resolução é impossível, imagine a seguinte classe e diga-me o que o compilador deve fazer:
class Foo<T> {
public Foo<U>(U x) { }
}
var x = new Foo(1);
Obrigado Konrad, é uma boa resposta (+1), mas apenas para expandir.
Vamos fingir que C# tem uma função construtora explícita:
//your example
var x = new Foo( 1 );
//becomes
var x = Foo.ctor( 1 );
//your problem is valid because this would be
var x = Foo<T>.ctor<int>( 1 );
//and T can't be inferred
Você está certo ao dizer que o primeiro construtor não pode ser inferido.
Agora vamos voltar para a aula
class Foo<T>
{
//<T> can't mean anything else in this context
public Foo(T x) { }
}
//this would now throw an exception unless the
//typeparam matches the parameter
var x = Foo<int>.ctor( 1 );
//so why wouldn't this work?
var x = Foo.ctor( 1 );
Claro, se eu adicionar seu construtor novamente (com seu tipo alternativo), teremos uma chamada ambígua - exatamente como se uma sobrecarga normal de método não pudesse ser resolvida.