.NET - Generics revisitada


O conceito de Generics foi introduzido a partir da versão 2.0 da .NET Framework. Nesta versão foi introduzido o namespace System.Collections.Generic, que contém as classes que suportam este conceito, como a List, Queue, Stack , LinkedList e outros recursos que podem ser usados efetivamente em seus programas.

Nas versões anteriores a 2.0, a generalização era realizada pela conversão de tipos de e para a base do tipo universal System.Object; Generics fornece uma solução para essa limitação em tempo de execução.

Essa limitação pode ser demonstrada com a ajuda da classe de coleção ArrayList do NET Framework. ArrayList é uma classe de coleção altamente conveniente que pode ser usada sem modificação para armazenar qualquer tipo de referência ou valor.

Mas esta conveniência tem um custo. Qualquer tipo de referência ou valor que é adicionado a um ArrayList é implicitamente convertido via typecast para System.Object. Se os itens são tipos de valor, eles devem sofrer um boxing quando adicionado à lista, e unboxing quando eles são recuperados. O coersão , boxing e unboxing são operações que degradam o desempenho, o efeito de boxing e unboxing pode ser bastante significativo em cenários onde você deve percorrer grandes coleções.

    //
    ArrayList list1 = new ArrayList();
    list1.Add(3);     
//com boxing e casting
    list1.Add(105);
    //...
    ArrayList list2 = new ArrayList();
    list2.Add("Primeiro item");  
//com boxing e casting
    list2.Add("Segundo item");
    //...

Obs: Boxing: É a conversão de um Value Type para um Reference Type e Unboxing: É quando um Reference Type (object) volta a ser um Value Type.

Então, o que nós precisamos é ter a flexibilidade do ArrayList, mas com mais eficiência e de forma a fornecer algumas formas de verificação de tipo pelo compilador, sem sacrificar a capacidade de reutilização da mesma classe com diferentes tipos de dados.

O ideal seria termos um ArrayList com um parâmetro de tipo , não é mesmo ???

Pois é justamaente isso que Generics oferece.

Usando Generics não temos mais a necessidade de que todos os itens a sejam convertidos Object e também permite que o compilador faça alguma verificação de tipo.

Na coleção de lista genérica <T>, no namespace System.Collections.Generic, a mesma operação de adicionar itens à coleção ficaria assim:

    List<int> list1 = new List<int>();
    list1.Add(3);                // Sem boxing, sem casting
    list1.Add("First item");   // erro de compilação

No código , a sintaxe só acrescentou com lista <T> onde T em comparação com ArrayList é o tipo de argumento na declaração e instanciação. Dessa forma usando este recurso, você pode criar uma lista que alem de ser mais segura do que ArrayList é também significativamente mais rápida, especialmente quando os itens da lista são tipos de valor.

Quando você usa Generics, está criando classes ou métodos que usam um tipo genérico, ao invés de um tipo específico. Por exemplo, ao invés de criar um tipo específico, você pode criar uma classe lista reutilizável usando Generics.

Para ficar mais claro vamos dar um exemplo usando uma classe genérica e uma classe fortemente tipada.

Usando Generics na prática

Abra o Visual C# 2010 Express Edition e crie um novo projeto do tipo Console Application com o nome UsandoGenerics;

A seguir inclua uma nova classe no projeto chamada MinhaLista.cs e a seguir digite o código abaixo para definir a classe Genérica MinhaLista:

Neste código, MinhaLista é construída usando um ArrayList. Mas seus métodos e indexador são fortemente tipados. Aqui <MeuTipo> é realmente apenas um espaço reservado para qualquer tipo que você escolheu ao criar a classe. Este espaço reservado é definida entre parênteses angulares(<>) após o nome da classe.

Agora, vejamos como criar uma instância da classe MinhaLista e incluir alguns valores:

Se desejarmos incluir strings na lista devemos fazer assim:

Podemos ir além definindo mais de um tipo de nossa classe base. Assim podemos ter :

using System.Collections;

namespace Generics
{
    public class MinhaLista<MeuTipo ,
MeuTipo2 >
    {
        private ArrayList m_lista = new ArrayList();
        public int Add(MeuTipo value)
        {
            return m_lista.Add(value);
        }
      
public int Add(MeuTipo2 value)
        {
            return m_lista.Add(value);
        }

        public void Remove(MeuTipo value)
        {
            m_lista.Remove(value);
        }

        public MeuTipo this[int index]
        {
            get
            {
                return (MeuTipo)m_lista[index];
            }
            set
            {
                m_lista[index] = value;
            }
        }
    }
}
namespace Generics
{
class Program
{
static void Main(string[] args)
{
   MinhaLista<int,string> inteiros = new MinhaLista<int,string>();
   //incluindo inteiros e string na lista
   inteiros.Add(100);
   inteiros.Add(250);
   inteiros.Add("Macoratti");

}
}
}

Vejamos agora um exemplo com as seguintes características:

  1. Uma classe fortemente tipada chamada "AlunosLista" ;
  2. Uma classe Genérica "ListaPersonalizada<T>" ;
  3. Uma classe "Aluno";

- A classe AlunosLista deve aceitar somente objetos do tipo Aluno;
- A classe ListaPersonalizada<T> pode aceitar qualquer tipo especificado em T;

Vejamos agora o código:

1- Classe Aluno - representa um Aluno. Define as propriedades Nome e Idade e cria uma instância de Aluno;


namespace Generics
{
    public class Aluno
    {
        private string _nome;
        private int _idade;

        public string Nome
        {
            get { return _nome; }
            set { _nome = value; }
        }
        public int Idade
        {
            get { return _idade; }
            set { _idade = value; }
        }
        /// <summary>
        /// Cria uma instância de ALuno
        /// </summary>

        public Aluno(string _nome, int _idade)
        {
            Nome = _nome;
            Idade = _idade;
        }
   }
}

2- A classe AlunoLista : Herda de IEnuemerable e define o métodos: Add,Remove, Removeat, Count e GetEnumerator.

using System;
using System.Collections;

namespace Generics
{
    public class AlunosLista : IEnumerable
    {
        private ArrayList alista = new ArrayList();

        public int Add(Aluno value)
        {
            try
            {
                return alista.Add(value);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public void Remove(Aluno value)
        {
            try
            {
                alista.Remove(value);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public void RemoveAt(int index)
        {
            try
            {
                alista.RemoveAt(index);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public int Count
        {
            get { return alista.Count; }
        }

        /// <summary>
        /// Retorna um enumerator que itera através de AlunosLista
        /// </summary>

        public IEnumerator GetEnumerator()
        {
            try
            {
                return alista.GetEnumerator();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

    }
}

3- Classe ListaPersonalizada <T> - Define a classe usando Generics de um tipo genérico T. Define os métodos Add, Remove, Count, GetEnumerato.

using System;
using System.Collections;
using System.Collections.Generic;

namespace Generics
{
    class ListaPersonalizada<T> : IEnumerable, IEnumerable<T>
    {
        private ArrayList alist = new ArrayList();

       
/// <summary>
        /// Inclui um novo valor do tipo T
        /// </summary>
        /// <param name="value">Objeto do tipo T</param>
        /// <returns>Retorna o indice</returns>

        public int Add(T value)
        {
            try
            {
                return alist.Add(value);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

    
   /// <summary>
        /// Remove o objeto de ListaPersonalizada
        /// </summary>
        /// <param name="value">Objeto do tipo T</param>

        public void Remove(T value)
        {
            try
            {
                alist.Remove(value);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

       
/// <summary>
        /// Obtem o contador do objeto ListaPersonalizada
        /// </summary>

        public int Count
        {
            get { return alist.Count; }
        }

       
/// <summary>
        ///  Retorna um enumerator que itera através de coleção
        /// </summary>

        public IEnumerator GetEnumerator()
        {
            try
            {
                return alist.GetEnumerator();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

      
 /// <summary>
        /// Retorna um enumerator que itera através de coleção
        /// </summary>

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            try
            {
                return (IEnumerator<T>)alist.GetEnumerator();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

a-) Para usar a classe AlunosLista fortemente tipada podemos fazer:

using System;

namespace Generics
{
    class Program
    {
        static void Main(string[] args)
        {
   
            Aluno macoratti = new Aluno("Macoratti", 42);
            Aluno miriam = new Aluno("Miriam", 32);

            ///Usando AlunosLista fortemente tipada
            AlunosLista mc = new AlunosLista();
            mc.Add(macoratti);
            mc.Add(miriam);

            Console.WriteLine("Usando AlunosLista fortemente tipada");
            foreach (Aluno s in mc)
            {
                Console.WriteLine("Nome  : " + s.Nome);
                Console.WriteLine("Idade : " + s.Idade);
            }
        }
    }
}

Para usar as classes definidas vamos incluir o código a seguir no método Main();

using System;

namespace Generics
{
    class Program
    {
        static void Main(string[] args)
        {

            Aluno macoratti = new Aluno("Macoratti", 42);
            Aluno miriam = new Aluno("Miriam", 32);

            ///Usando AlunosLista fortemente tipada
            AlunosLista mc = new AlunosLista();
            mc.Add(macoratti);
            mc.Add(miriam);

            Console.WriteLine("Usando AlunosLista fortemente tipada");
            foreach (Aluno s in mc)
            {
                Console.WriteLine("Nome  : " + s.Nome);
                Console.WriteLine("Idade : " + s.Idade);
            }

            ///Cria uma lista de objetos Aluno usando generic
            ListaPersonalizada<Aluno> student = new ListaPersonalizada<Aluno>();
            student.Add(macoratti);
            student.Add(miriam);

            Console.WriteLine("Usando uma lista de objetos ALuno");
            foreach (Aluno s in student)
            {
                Console.WriteLine("Nome  : "  + s.Nome );
                Console.WriteLine("Idade : "  + s.Idade);
            }

            ///Cria uma lista de objetos alunos usando um método generico
            ListaPersonalizada<int> intlist = new ListaPersonalizada<int>();
            intlist.Add(1);
            intlist.Add(2);

            Console.WriteLine("Usando uma lista de valores strings via generics</B></U><BR>");
            foreach (int i in intlist)
            {
                Console.WriteLine("Index : " + i.ToString() );
            }

            ///Cria uma lista de objetos alunos usando
            ListaPersonalizada<string> strlist = new ListaPersonalizada<string>();
            strlist.Add("Um");
            strlist.Add("Dois");

            Console.WriteLine("Usando uma lista de valores com generics");
            foreach (string str in strlist)
            {
                Console.WriteLine("Indice : " + str);
            }
           Console.ReadKey();
        }
    }
}

Executando o código acima iremos obter:

Definindo Restrições

Agora, vamos ver como aplicar restrições de forma podermos restringir os tipos que são permitidos. Por exemplo, se queremos limitar nosso tipo apenas para os tipos que implementam a interface IDisposable, podemos fazê-lo da seguinte forma:

using System;

namespace Generics
{
    public class MinhaLista<MeuTipo>
where MeuTipo : IDisposable
    {
    }
}

Aqui estamos usando a palavra reservada where para especificar a restrição.

A seguir temos os principais tipos de restrições que podemos usar usando where:

where T: struct

Restringe T a tipos de valor (inteiross, booleanos, e estruturas mais complexas)

where T : class

Restringe T a tipos de referência.

where T : new()

Com isso garantimos que T terá um construtor publico sem argumentos.
Geralmente usado se você precisar criar uma nova instância do tipo.

where T : <base class>

Restringe T a um tipo de referência. (Não garante que seja possível criar novas instâncias.)

where T : <interface>

Este tipo de restrição garante que T implementa uma dada interface.

where T : R

O tipo fornecido por T precisa ser do mesmo tipo ou derivado do tipo fornecido por R.

Generics não estão limitadas a apenas  classes; elas também podem ser usadas em estruturas, interfaces e delegados. Podemos até usar Generics para parametrizar um método por tipo.

Em geral, Generics permitem aos programadores testar e implantar o código uma vez, e depois reutilizar o código para uma variedade de tipos de dados diferentes. Além disso, os genéricos são verificados em tempo de compilação. Quando o programa instancia uma classe genérica com um parâmetro do tipo fornecido, o parâmetro de tipo só pode ser do tipo de seu programa especificado na definição da classe.

Eu sei é apenas Generics mas eu gosto...

"Mas ele disse: Antes bem-aventurados os que ouvem a palavra de Deus e a guardam." Lucas(5:28)

Referências:


José Carlos Macoratti