Esticando a inferência de tipos
Nota: Código em itálico não corresponde à sintaxe do C# 3.0.
Inferência do tipo de variáveis locais
O C# 3.0 trouxe-nos a possibilidade da inferência do tipo de uma variável local, cujo principal objectivo foi suportar o LINQ. O resultado de uma consulta (query) pode ser uma implementação de IEnumerable<T> ou IQueryable<T> uma instância de T onde T pode ser uma projecção, o que implica que T é um tipo anónimo.
Tomemos a consulta seguinte como exemplo:
from p in persons
select new { Name = p.FirstName + " " + p.LastName, Age = p.Age };
Se persons for uma implementação de IEnumerable<Person>, o resultado da consulta será uma implementação de IEnumerable<T> onde T é um tipo anónimo.
Coloca-se então uma questão: Como é que declaramos uma variável para receber o resultado da consulta?
A solução encontrada foi a introdição da nova palavra-chave var que instrui o compilador para determinar o tipo do resultado da consulta. Sendo assim, tudo é que é ncessário para declarar uma variável para receber resultado da consulta é:
var q =
from p in persons
select new { Name = p.FirstName + " " + p.LastName, Age = p.Age };
Uma vez que o compilador consegue inferir o tipo de algo que arece tão complexo, é óbvio que será capaz de inferir o tipo de algo tão simples directo como:
var v = new Dictionary<Point, Stack<Person>>();
Obviamente, é o mesmo que:
Dictionary<Point, Stack<Person>> v = new Dictionary<Point, Stack<Person>>();
com menos digitação e menor margem para erros de digitação.
Inferência do tipo de campos
A mesma técnica poderia ser aplicada à inferência do tipo de campos:
class Class
{
private someDicionary = new Dictionary<string, List<string>>();
// ...
}
com os mesmos benefícios de menos digitação e menor margem para erros de digitação.
Se a inferência do tipo de campos fosse aplicada a campos públicos, deveria ser aplicado o seguinte constrangimento: o tipo inferido não pode ser ou ter como parâmtero um tipo anónimo.
Mas eu não recomendaria o seu uso porque facilmente uma simples modificação na inicialização do campo poderia trazer alterações menos óbvias à interface pública da classe.
Inferência do tipo de parâmetros e retorno de métodos
Dado que a inferência de tipos seria possível fora de métodos, deveria ser permitida em parâmetros e valores de retorno? Algo como isto:
class SomeClass
{
private listOfStuff = new List<Stuff>();
public void DoIt()
{
if (CreateList(out this.listOfStuff))
{
ProcessList(ref this.listOfStuff);
this.listOfStuff = TransformList(this.listOfStuff);
}
}
private bool CreateList(var out list)
{
list = new List<Stuff>();
return true;
}
private void ProcessList(var ref list)
{
// ...
}
private var TransformList(var list)
{
return list;
}
}
que tem um aspecto algo estranho e confuso, no mínimo. Não deveríamos ir por aí.
Inferência de construtores
Desde o meu primeiro contacto com a inferência do tipo de variaveis locais que tenho a sensação de que se poderia ter ido mais longe.
Olhemos para este conjunto hipotético de declarações:
Dictionary<Point, Stack<Person>> v = new();
Point p = new(1, 2);
Será fácil, tanto para o compilador como para um humano que leia o código, determinar que é o mesmo que:
Dictionary<Point, Stack<Person>> v = new Dictionary<Point, Stack<Person>>();
Point p = new Point(1, 2);
E isto pode ser mais poderoso que a inferência do tipo de variáveis locais e usado em mais cenários:
class Class
{
private Dictionary<string, List<string>> someDicionary = new();
private Point p;
public Class()
{
this.p = new(1, 1);
}
public void SomeMethod()
{
AnotherMethod(new());
}
private void AnotherMethod(List<string> arg)
{
// ...
}
}
Onde parar?
O compilador poderia com igual facilidade inferir o tipo da variável local neste caso:
public void SomeMethod()
{
var v = new();
AnotherMethod(v);
}
Queremos ir por aí? Não me parece.
Ambiguidades
O C# 3.0 trouxe-nos também inicializadores de objectos. Em vez disto:
XmlReaderSettings settings = new XmlReaderSettings();
settings.CheckCharacters = false;
settings.IgnoreWhitespace = true;
XmlReader reader = XmlReader.Create("someURI", settings);
podemos apenas escrever isto:
XmlReaderSettings settings = new XmlReaderSettings() { CheckCharacters = false, IgnoreWhitespace = true };
XmlReader reader = XmlReader.Create("someURI", settings);
ou isto:
XmlReader reader = XmlReader.Create("someURI", new XmlReaderSettings() { CheckCharacters = false, IgnoreWhitespace = true });
Não seria bom escrever apenas isto?
XmlReaderSettings settings = new() { CheckCharacters = false, IgnoreWhitespace = true };
Os inicializadores de objectos do C# 3.0 também permitem omitr os parentesis do construtor:
XmlReaderSettings settings = new XmlReaderSettings { CheckCharacters = false, IgnoreWhitespace = true };
que levaria a:
XmlReaderSettings settings = new { CheckCharacters = false, IgnoreWhitespace = true };
que tem a mesma forma que a criação de um objecto de um tipo anónimo e poderia causar algumas ambiguidades.
Mas seria bom escrever apenas isto:
XmlReader reader = XmlReader.Create("someURI", new { settings.CheckCharacters = false, settings.IgnoreWhitespace = true });
Não seria?