Access Violation

terça-feira, outubro 25, 2005

Generics II
No post anterior discuti um pouco sobre Generics; apresentei alguns problemas e como os mesmos são resolvidos/atenuados com a utilização deste conceito.

De uma forma mais ampla, podemos usar o conceito de generics quando uma classe/algoritmo pode ser aplicado a mais de um tipo de dados, ou seja, esta classe/algoritmo é independente do tipo de dado em questão; ao invés de criar várias versões da mesma classe (uma para cada tipo de dado desejado), criamos uma classe genérica e deixamos o compilador (no caso do .Net o compilador/Framework) trabalhar por nós.

O exemplo apresentado no post anterior é um caso clássico: em um programa podemos ter a necessidade de armazenar coleções de diversos tipos de dados diferentes; uma solução é implementar classes que armazenam tipos de dados específicos, contudo esta, geralmente, não é uma boa solução, visto que teremos duplicação de código, trabalho, etc. Uma segunda alternativa é a solução adotada até a versão 1.1 do .Net Framework, ou seja, escrever coleções de dados que manipulam o tipo de dado base (no .Net Object); pelas razões discutidas no post anterior (entre outras) esta também não é uma solução muito atraente; uma terceita alternativa seria mesclar as duas primeiras, ou seja, ter uma implementação da coleção que aceita a classe Object e escrever versões mais especializadas que utilizam esta implementação (melhorou bastante mas ainda temos que escrever e dar manutenção nas versões específicas). Generics nos permite escrever classes/algorítmos sem nos preocuparmos com o tipo de dado manipulado; para ser mais preciso manipulamos um tipo de dado que sera especificado pelo usuário do nosso código.

Assim, um tipo genérico (por exemplo a classe List<T> do namespace System.Collections.Generic) representa apenas um modelo, ou seja, não podemos criar instâncias deste tipo. Por exemplo o seguinte código não compila:

IList list = new System.Collections.Generic.List();

Se você tentar compilar este trecho de código em um programa C# o compilador emitirá um erro semelhante a:

TestGenericCollections.cs(9,3): error CS0305: Using the generic type 'System.Collections.Generic.List<T>' requires '1' type arguments

Este erro nos informa que o tipo List requer um type parameter, que nada mais é que um tipo de dados que será aplicado ao tipo genérico. Em outras palavras o compilador não tem como decidir se desejamos uma lista de bananas, nomes, idades, etc. Este dado (o tipo de lista que desejamos) deve ser informado ao compilador utilizando o type parameter T. Assim, para que o exemplo compile temos que dizer ao compilador qual é este tipo:

IList<string> list = new System.Collections.Generic.List<string>();
Neste exemplo dizemos que o tipo String é um type parameter, ou seja, especifica o tipo que o compilador deve utilizar toda vez que T aparecer como um tipo na classe List).

Um tipo generic deve possuir um ou mais type parameters que devem ser substuídos por tipos específicos no momento do uso do mesmo. Acho que aqui vale um exemplo:

0: using System;
1:
2: class TestGeneric<T>
3: {
4: public TestGeneric(T i)
5: {
6: data = i;
7: }
8:
9: public T Data
10: {
11: get
12: {
13: return data;
14: }
15:
16: set
17: {
18: data = value;
19: }
20: }
21:
22: public string Name
23: {
24: get
25: {
26: return name;
27: }
28: }
29:
30: private String name;
31: private T data;
32: }
33:
34: public class TestGenericDriver
35: {
36: private static void Main()
37: {
38: TestGeneric<string> t = new TestGeneric<string>("Adriano");
39: Type tt = t.GetType();
40:
41: Type[] defparams = tt.GetGenericArguments();
42: foreach (Type tp in defparams)
43: {
44: Console.WriteLine("\r\nType parameter: {0}", tp.Name);
45: }
46: }
47: }
Neste exemplo definimos um tipo genérico (TestGeneric) que espera um Type Parameter (T) (Linha 2). Este tipo declara uma variável de instância data (Linha 31) do tipo T e também uma propriedade Data (Linha 9). Na linha 38 criamos uma instância da classe TestGeneric usando string como tipo para T, ou seja, tanto a variável de instância data quanto a propriedade Data assumem o tipo string para a variável t (linha 38).

Quando associamos um tipo a um type parameter dizemos que estamos criando uma instância do tipo genérico. Assim na linha 38 o trecho de código:
TestGeneric<string> t = new TestGeneric<string>("Adriano");
instancia o tipo TestGeneric (não confundir a instância de uma classe, ou seja, um objeto, com a instância de um tipo genérico) com o type parameter como string.

Da mesma forma ao escrevermos o seguinte código:
TestGeneric<int> ti;
estamos instanciando o tipo TestGeneric com T como int.

Apesar de neste exemplo em particular o tipo genérico possuir apenas um type parameter nada impede de definirmos tipos com dois ou mais type parameters.

Nos próximos posts pretendo discutir um pouco mais sobre:
  • constrains.
  • diferenças entre generics e templates C++.
  • Palavra reservada default.
  • Representação em runtime.
Adriano.

[+/-] mostrar/esconder este post