Covariância e Contravariância em C#, Parte Dois: Covariância de Array
C# implementa variância de duas maneiras. Hoje apresentarei a maneira incorreta.
Desde o C# 1.0, arrays onde o tipo do elemento é um tipo por referência são covariantes. Isto é perfeitamente correto:
Animal[] animais = new Girafa[10];
Desde que Girafa seja menor que Animal(isto é, Girafa herda de Animal), fazer um array dela é uma operação sobre tipos covariantes. Girafa[] é menor que Animal[], então sua instância se enquadra na variável.
Infelizmente, este tipo particular de covariância não é totalmente correto. Foi acrescentada à CLR porque o Java a implementa e os designers da CLR queriam uma linguagem parecida com o Java. Então foi adicionado ao C#, porque o recurso estava disponível na CLR. Esta decisão foi muito controversa no desenvolvimento, mas não há nada que possamos fazer em relação a isso agora.
Porque está incorreto? Porque deve ser sempre permitido colocar Tartaruga em um array de Animal. Com a covariância da array na linguagem e na runtime você não pode garantir que uma array de Animal pode aceitar uma Tartaruga porque por trás dela pode ser um array de Girafa.
Isto significa que tornamos um erro que podia ser pego pelo compilador em um que só pode ser pego no momento da execução. Isso também significa que toda vez que você colocar um objeto em um array, temos de fazer uma verificação em tempo de execução para garantir que o tipo funciona ou se gera uma exceção. Isso é potencialmente caro se você está colocando muitas coisas no array.
Um exemplo que você pode testar e verificar que realmente passa pelo compilador, mas gera uma exceção em tempo de execução:
class Animal { }
class Girafa : Animal { }
class Tartaruga : Animal { }
Animal[] animais = new Girafa[10];
animais[0] = new Tartaruga();
Na próxima parte vamos discutir uma espécie de variância que foi adicionado ao C# 2.0, que não tem problemas como esse.
Covariância e Contravariância em C#, Parte Um
Vou escrever sobre uma série de artigos do Eric Lippert sobre Covariância e Contravariância. Pretendo dividir os artigos em partes como foi feito no original, pra deixar as coisas mais organizadas e fazer mudanças que forem necessárias pra um melhor entendimento.
Nesta primeira parte vamos entender alguns conceitos sobre tipos.
Primeiramente temos que entender que para dois tipos X e Y, pelo menos uma das seguintes afirmações é verdadeira:
X é maior do que Y.
X é menor do que Y.
X é igual a Y.
X não tem nenhuma relação com Y.
Considere uma hierarquia constituída de: Animal, Mamífero, Reptil, Girafa, Tigre, Cobra e Tartaruga, com seus relacionamentos (Mamífero é uma subclasse de Animal, etc). Mamífero é maior do que Girafa, menor que Animal, e, evidentemente, é igual à Mamífero. Mas Mamífero não é nem maior, nem menor, nem igual a Reptil, é apenas diferente.
Imagine que você tenha uma variável, toda variável tem um tipo que lhe é associado. Em runtime você pode armazenar um objeto que é uma instância de um tipo igual ou menor, ou seja, uma variável do tipo Mamífero pode ter uma instância de Girafa armazenados nela, mas não uma Tartaruga.
Esta idéia de armazenamento de um objeto em um local tipado é um exemplo específico de um princípio mais geral chamado de "princípio da substituição". Isto é, em muitos contextos, podemos substituir uma instância de um tipo "menor" por um "maior".
Agora podemos falar de variância. Considere uma "operação", que manipula tipos. Se o resultado da operação aplicada a qualquer X e Y e sempre resulta em dois tipos X' e Y' com o mesmo relacionamento X e Y a operação é considerada covariante. Se a operação inverte a "grandeza" e a "pequeneza" sobre os seus resultados, mas mantém a igualdade e independência, a operação é considerada "contravariante".
Isso é totalmente imaginário e provavelmente não muito claro. Na próxima parte vamos analisar como o C# implementa variância.
MSDN Magazine - Janeiro 2008
Está disponível a edição de Janeiro da MSDN Magazine. Assuntos de capa:
• IIS 7.0: Aprimore seu aplicativo com o pipeline integrado do ASP.NET
Mike Volodarsky
• World Ready: Uma volta ao mundo com aplicativos ASP.NET AJAX
Guy Smith-Ferrier
• WCF Syndication: Programação HTTP com WCF e o .NET Framework 3.5
Justin Smith
• SQL Server: Encontrar dados ocultos para otimizar o desempenho do aplicativo
Ian Stirk
• Look It Up: Gerenciando entidades de segurança de diretório no .NET Framework 3.5
Joe Kaplan e Ethan Wilansky
Novos recursos no C#3.0
Vou falar sobre alguns novos recursos que vieram na nova versão do C#.
Começando pelas Propriedades Automáticas.
Antigamente quando a gente criava as propriedades das nossas classes, faziamos um código como esse:
7 public class Carro
8 {
9 private string _modelo;
10 private string _marca;
11 private int _ano;
12 private string _cor;
13
14 public string Modelo
15 {16 get { return _modelo; }
17 set { _modelo = value; }18 }
1920 public string Marca
21 {22 get { return _marca; }
23 set { _marca = value; }24 }
2526 public int Ano
27 {28 get { return _ano; }
29 set { _ano = value; }30 }
3132 public string Cor
33 {34 get { return _cor; }
35 set { _cor = value; }36 }
37 }Como você pode notar, as propriedades não tem nenhum tratamento lógico, servindo como se fosse um acesso direto aos campos. Então porque não expor os campos diretamente? Basicamente porque no futuro você pode querer implementar algum tratamento, assim se você tiver usado propriedades, as classes que usam suas propriedades não precisarão ser recompiladas.
Sendo muito comum o uso de propriedades como as que vimos acima, no C#3.0 foi implementado as Propriedades Automáticas, que substituem esse código "padrão". O código anterior ficaria assim:
7 public class Carro
8 {
9 public string Modelo { get; set; }
10 public string Marca { get; set; }
11 public int Ano { get; set; }
12 public string Cor { get; set; }
13 }
Quando o compilador encontrar um get/set vazio como estes, ele automáticamente criará os campos privados para a classe e implementará as propriedades públicas get/set.
Diferentemente de criar campos públicos, poderemos no futuro criar as implementações lógicas, sem ter que mudar nenhum componente externo que referencia nossa classe.
Inicializadores de Objetos
Usamos muitas propriedades quando estamos programando e é muito comum códigos como esse:
13 Carro car = new Carro();
14 car.Modelo = "Punto";15 car.Marca = "Fiat";
16 car.Ano = 2007;17 car.Cor = "Azul";
Agora podemos escrever isso de outra forma, mais simples e concisa, usando os "object Initializers". Seu código pode ser escrito assim:
Carro car = new Carro { Modelo = "Punto", Marca = "Fiat", Ano = 2007, Cor = "Azul" };
E o compilador irá se encarregar de instanciar a classe e setar as propriedades.
É interessante que podemos inicializar objetos aninhados, como mostrado:
Carro car = new Carro { Modelo = "Punto", Marca = "Fiat", Ano = 2007, Cor = "Azul", Fabric = new Fabricante { Nome = "Industria Metal", CNPJ = "12312" } };
Inicializadores de Coleção
Os inicializadores de objeto também facilitam a maneira como adicionamos itens a uma coleção. Se quisermos adicionar objetos Carro a uma List devemos escrever o seguinte código:
14 List<Carro> Carros = new List<Carro>();
1516 Carros.Add(new Carro { Modelo = "Punto", Marca = "Fiat", Ano = 2007, Cor = "Azul" });
17 Carros.Add(new Carro { Modelo = "Punto", Marca = "Fiat", Ano = 2007, Cor = "Vermelho" });18 Carros.Add(new Carro { Modelo = "Punto", Marca = "Fiat", Ano = 2007, Cor = "Preto" });
Economizando muitas linhas de código. E podemos diminuir ainda mais, usando Inicializadores de coleção, não precisamos escrever vários métodos Add:
14 List<Carro> Carros = new List<Carro>{
15 new Carro { Modelo = "Punto", Marca = "Fiat", Ano = 2007, Cor = "Azul" },
16 new Carro { Modelo = "Punto", Marca = "Fiat", Ano = 2007, Cor = "Vermelho" },
17 new Carro { Modelo = "Punto", Marca = "Fiat", Ano = 2007, Cor = "Preto" }
18 };