Co e Contra Variância em Interfaces


Os parâmetros genéricos foram recursos que foram incluídos no C# em sua versão 2.0. Desde então, podemos criar classes, interfaces, delegates, etc., que podem determinar um ou vários parâmetros e, consequentemente, utilizá-los definindo o tipo que acharmos necessário. No momento da criação/uso deste tipo, definimos o tipo do(s) parâmetro(s), e todos os membros em que o mencionamos passam a operar com ele, mas já fazendo diversas verificações ainda em tempo de compilação.

A partir do momento que podemos definir qualquer tipo, é comum queremos mencionar tipos que estão relacionados por herança, mas apesar disso ser suportado, algumas coisas que são comuns e fazemos normalmente, são barrados se utilizarmos em conjunto com tipos genéricos. Um caso específico que quero mencionar aqui, é o uso deste tipo genérico com interfaces. Para melhor ilustrar isso, vamos nos basear na estrutura de classes abaixo:

Note que Documento serve como base para todo e qualquer documento controlado pelo sistema. A partir deste momento, podemos criar interface que será responsável por definir um criador de objetos, e o tipo passa a ser determinado pelo parâmetro genérico T. Note que T é usado como retorno pelo método Criar.

interface ICriador<T>
{
T Criar();
}

Com esta interface, podemos construir uma classe que representará o criador de objetos padrão do sistema, e para demonstração, tudo o que vamos fazer é instanciar o tipo definido em T, o que nos obriga a defnir uma constraint, ou seja, que o tipo T possua um construtor público.

class CriadorPadrao<T> : ICriador<T> where T : new()
{
public T Criar()
{
return new T();
}
}

A ideia desta classe, é concentrar a criação do objetos, ou melhor, dos documentos utilizados pelo nosso sistema. Sendo assim, como queremos trabalhar da forma mais genérica possível, ou seja, não quero lidar diretamente em meu código com ICriador<Cnpj>, ICriador<Cpf>, etc., isso me obriga a criar uma variável do tipo ICriador<Documento> (Documento é a classe base). Com isso, a qualquer momento atribuimos à esta variável a instância do criador genérico, apontando em T o tipo Cnpj, Cpf, etc., assim como sugere o código abaixo:

ICriador<Documento> c = new CriadorPadrao<Cnpj>();
Console.WriteLine(c.Criar());

Apesar de CriadorPadrao implementar a interface ICriador, e mesmo pelo fato de Cnpj (tipo genérico definido em T) herdar de Documento, o C# não permite a compilação deste código. O fato aqui é que toda interface genérica, até a versão 3.0 do C#, é sempre invariante. Apesar dos tipos – isoladamente – estarem relacionados, CriadorPadrao<Cnpj> não implementa a interface ICriador<Documento>, o que viola a regra do polimorfismo.

A versão 4.0 do C# introduziu um recurso chamado de covariância. Ele nos permitirá, com um ligeira mudança na interface, fazer com que o código acima seja compilado e executado com sucesso. A mudança qual me refiro, é a introdução da keyword “out” antes do tipo genérico T, assim como mostrado abaixo:

interface ICriador<out T>
{
T Criar();
}

Isso dirá ao compilador que T somente será usado como retorno de métodos e/ou propriedades de somente leitura. Como o CriadorPadrao<Cnpj> vai retornar a instância da classe Cnpj no método Criar, ela pode ser acessada através da interface ICriador<Documento>, afinal, Documento é a classe base dela. Se definirmos na interface um parâmetro do tipo T, como o acesso à instância é através de ICriador<Documento>, poderia entrar qualquer coisa, ou seja, uma instância da classe Cnh, o que fará com que uma exceção seja disparada. Abaixo temos uma imagem que ajuda a entender:

Em um outro cenário, poderíamos ter uma interface que ao invés de retornar, passa a receber um tipo T. Dado um tipo, a interface define a estrutura de validação, que em seu método Validar recebe um parâmetro T, e devolve um bool indicando o resultado da validação. Abaixo temos a definição desta interface:

interface IValidador<T>
{
bool Validar(T documento);
}

Teremos também um validador padrão, que define a estrutura de validação genérica para qualquer tipo. Note que temos a classe ValidadorPadrao, que define T e propaga para a interface e, consequentemente, para o método Validar. Note que neste momento, o T está entrando.

class ValidadorPadrao<T> : IValidador<T>
{
public bool Validar(T documento)
{
//Validação
return true;
}
}

Agora a situação é que temos em mãos um validador exclusivo para Cnpj, e atribuímos a ele a instância da classe ValidadorPadrao, definindo T como Documento, ou seja, a classe base de todos os documentos, incluindo o próprio Cnpj.

IValidador<Cnpj> v = new ValidadorPadrao<Documento>();
v.Validar(new Cnpj());

Ao compilar o código, novamente temos um erro. Novamente, apesar de ValidadorPadrao implementar a interface IValidador, a atribuição não será possível. Apesar de estarmos tomando cuidado de passar para o método Criar um Cnpj (que herda de Documento), isso daria margem para definir o validador com um tipo sem relação alguma, como por exemplo, uma string, e através do método Validar passar a instância de CNPJ, e durante a execução, um erro ocorreria porque não é possível fazer tal conversão.

Para resolver isso, temos a contravariância. Também com uma pequena mudança na criação da interface, teremos um código mais flexível. Aqui teremos que adicionar antes do parâmetro T a keyword “in”, assim como é mostrado abaixo:

interface IValidador<in T>
{
bool Validar(T documento);
}

Isso dirá ao compilador que T somente será usado como parâmetros de entrada em métodos e/ou propriedades de somente escrita. Como o ValidadorPadrao<Documento> vai receber uma instância da classe Documento no método Validar, ela pode ser definida através da interface IValidador<Cnpj>, afinal, Cnpj é um tipo de Documento. Se definirmos na interface uma saída do tipo T, como o acesso à instância do validor é através de IValidador<Cnpj>, poderia retornar qualquer coisa, ou seja, uma classe Cnh, o que fará com que uma exceção seja disparada, pois não é possível converter Cnh em Cnpj. Abaixo temos uma imagem que ajuda a entender:

Publicidade

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s