Desenvolvimento - C#

Generics com C# e .NET 2.0

Desde a versão 2.0 do .NET Framework, onde além de grandes novidades como Generics e Nullable Types, foram incluídos alguns delegates poderosíssimos que explicarei com mais detalhes logo adiante.

por Rodolfo Paoni



Desde a versão 2.0 do .NET Framework, onde além de grandes novidades como Generics e Nullable Types, foram incluídos alguns delegates poderosíssimos que explicarei com mais detalhes logo adiante. Acredito que a maioria dos desenvolvedores já estejam familiarizados com Generics, mas sempre vale a pena “refrescar a memória” com os conceitos. Farei um pequeno paralelo entre a versão 1.1 e 2.0 para que possamos observar claramente a sua evolução.

Quando trabalhávamos com .NET 1.1 normalmente desenvolvíamos uma classe que herdasse de CollectionBase para que tivéssemos tipagem forte ao invés de utilizar ArrayLists puros, que também eram (e ainda são) muito utilizados.

Imaginemos uma classe Usuario:

public class Usuario
{
private string _nome;

public string Nome
{
get { return this._nome; }
set { this._nome = value; }
}

private string _grupo;

public string Grupo
{
get { return this._grupo; }
set { this._grupo = value; }
}

public override string ToString()
{
return "Nome: " + Nome + " - Grupo: " + Grupo;
}
}

E a seguinte coleção de Usuários:

public class UsuarioCollection : CollectionBase
{
public int Add(Usuario u)
{
return base.List.Add(u);
}

public bool Contains(Usuario u)
{
return base.List.Contains(u);
}

public void Insert(int index, Usuario u)
{
base.List.Insert(index, u);
}

public Usuario this[int index]
{
get { return (Usuario)base.List[index]; }
set { base.List[index] = value; }
}

public void Remove(Usuario u)
{
base.List.Remove(u);
}
}

Uso da coleção

Usuario u1 = new Usuario();
u1.Nome = "Rodolfo";
u1.Grupo = "A";

Usuario u2 = new Usuario();
u2.Nome = "Rodrigo";
u2.Grupo = "B";

Usuario u3 = new Usuario();
u3.Nome = "Ronald";
u3.Grupo = "C";

UsuarioCollection uc = new UsuarioCollection();
uc.Add(u1);
uc.Add(u2);
uc.Add(u3);
// uc.Add(“Rodolfo”); // Erro em tempo de compilação

Console.WriteLine("Nome: " + uc[0].Nome);

foreach (Usuario u in uc)
{
Console.WriteLine(u.ToString());
}

Repare que apesar de termos tipagem forte, ainda temos problemas com relação a Casting, pois internamente ainda utilizamos um objeto do tipo ArrayList e temos uma grande perda de produtividade, pois é necessário cria uma classe para casa tipo de coleção desejado.

Utilizando Generics

Podemos criar uma classe genérica da seguinte forma:

class GenericClass<T>
{
private T _valor;
public T Valor
{
get { return this._valor; }
set { this._valor = value; }
}
}

T é o tipo que será atribuído à classe e ela terá uma property ‘Valor’ deste mesmo tipo. Repare que não existe Casting na recuperação dos dados, pois o compilador já sabe qual é o tipo atribuído à classe. Suponhamos uma classe Cliente com uma propriedade ‘Nome’ e um construtor sobrecarregado. Poderemos utilizar a classe ‘List’ em ‘System.Collections.Generic’ e definir o tipo da lista no momento da declaração.

Utilizando uma classe genérica

GenericClass<int> t1 = new GenericClass<int>();
t1.Valor = 10;
Console.WriteLine("Inteiro: " + t1.Valor);

GenericClass<string> t2 = new GenericClass<string>();
t2.Valor = "10";
Console.WriteLine("String: " + t2.Valor);

Utilizando List<T>

List<Cliente> clientes = new List<Cliente>();
clientes.Add(new Cliente("Rodolfo"));
clientes.Add(new Cliente("João"));
clientes.Add(new Cliente("Maria"));

//Erro em tempo de compilação, pois a lista foi definida como uma lista de "Cliente"
//clientes.Add(new Funcionario("Rodolfo"));

//Recuperando as informações com "foreach"
foreach (Cliente c in clientes)
{
Console.WriteLine("Nome: " + c.Nome);
}

Console.WriteLine("\n");

//Recuperando as informações com "Iterator"
IEnumerator<Cliente> enumer = clientes.GetEnumerator();

while (enumer.MoveNext())
{
Console.WriteLine("Nome: " + enumer.Current.Nome);
}

Console.WriteLine(string.Empty);

Temos também as chamadas Constraints em Generics onde podemos definir certas restrições neste tipo T atribuído no momento da declaração de uma classe genérica, como no exemplo em ‘GenericClass’. No exemplo acima a restrição é que o tipo T deve ser um tipo ‘Cliente’. Podemos também definir outros tipos de restrição:

where T : struct – Onde T seja uma estrutura

where T : class – Onde T seja uma classe

where T : new() – Onde T possua um construtor default

where T : U – Onde T herde de U

where T : <nome da classe> – Onde T herde de uma classe específica

where T : <nome da interface> – Onde T implemente uma interface

Exemplo:

class GenericClass<T> where T : Cliente // Onde T herde de "Cliente"
{
private T _valor;
public T Valor
{
get { return this._valor; }
set { this._valor = value; }
}
}

Por enquanto é só. No próximo artigo abordarei alguns delegates muito úteis, que apesar de fazerem parte do .NET Framework 2.0, ainda são pouco utilizados.

Até a próxima!

Rodolfo Paoni

Rodolfo Paoni - Rodolfo Paoni - Desenvolvedor .NET - Twitter: @rodolfopaoni