Como comentei neste post, pretendo falar um pouco mais sobre as melhorias introduzidas na versão 2.0 do C#.
Na realidade, muitas destas melhorias só são suportadas pois foram introduzidas também no .Net Framework.
Vou começar pelo assunto Generics, uma vez que venho do "mundo C++" e gosto muito do conceito de templates (que é semelhante)
Em primeiro lugar, Generics é uma tecnologia que nos permite tratar um tipo de forma genérica (ok, sem gozação). Um caso clássico é a implementação de coleções (por exemplo listas encadeadas, filas, pilhas, etc). Até a versão 2.0 do .Net Framework coleções eram implementadas de forma a tratar os dados como Object. Funciona, mas esta abordagem traz vários problemas:
- Uma vez armazenado na coleção, o objeto perde sua identidade, ou seja, tudo é tratado como object.
- Isto obriga o desenvolvedor a fazer casts
- Não é possível ao compilador verificar o correto uso da coleção quanto ao tipo de dado incluído na mesma.
- Performance (basicamente boxing/unboxing)
Vamos apresentar um exemplo:
1: using System.Collections;
2: using System;
3:
4: class TestCollection
5: {
6: public static void Main()
7: {
8: IList col = new ArrayList();
9:
10: col.Add("Adriano");
11: col.Add("Verona");
12:
13: String s = (String) col[0];
14: foreach (string str in col)
15: {
16: System.Console.WriteLine(str);
17: }
18:
19: col.Add(10);
20: foreach (string str in col)
21: {
22: System.Console.WriteLine(str);
23: }
24: }
25: }
Os problemas começam na linha 13 (que ilustra o ponto 2). Porque o compilador não permite que eu atribua col[0] para a variável s sem um cast explícito? Bom, porque o indexador [] da interface IList foi declarado como retornando um object e não poderia ser diferente! O único tipo de dados genérico disponível era o object!
Outro problema pode ser observado na linha 19. Como o método Add() espera um object o compilador não tem como verificar que esta operação não é válida. Provavelmente o desenvolvedor que escreveu este código ficará surpreso ao ser gerada uma exceção na linha 20.
Agora observer o próximo exemplo:
1: using System.Collections.Generic;
2: using System;
3:
4: class TestGenericCollection
5: {
6: public static void Main()
7: {
8: IList<String> strCol = new List<String>();
9:
10: strCol.Add("Str1");
11: strCol.Add("Str1");
12:
13: String s = strCol[0]; // Agora sim!!
14: Console.WriteLine(s);
15:
16: // Tentar compilar com o código abaixo descomentado gera um erro de compilação
17: // strCol.Add(42);
18: }
19: }
A primeira diferença que observamos é quanto ao namespace utilizado. Neste exemplo passamos a utilizar o namespace System.Collections.Generic ao invés do tradicional System.Collections.
Na linha 8 declaramos uma lista para armazenar strings (denotado pela sintaxe <String> que segue a interface IList e a classe List).
Na linha 13 observamos que podemos recuperar elementos da lista sem necessidade de casts. Você pode estar pensando: "ummm, o compilador deu uma de esperto e fez o cast implicito!! ou seja, sintaxe suggar"; você esta enganado... observe o código IL gerado no primeiro exemplo:
1: .method public hidebysig static void Main() cil managed
2: {
3: .entrypoint
4: // Code size 175 (0xaf)
5: .maxstack 2
6: .locals init (class [mscorlib]System.Collections.IList V_0,
7: string V_1,
8: string V_2,
9: class [mscorlib]System.Collections.Generic.IList`1<string> V_3,
10: class [mscorlib]System.Collections.IEnumerator V_4,
11: bool V_5,
12: class [mscorlib]System.IDisposable V_6)
13: IL_0000: nop
14: IL_0001: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
15: IL_0006: stloc.0
16: IL_0007: ldloc.0
17: IL_0008: ldstr "Adriano"
18: IL_000d: callvirt instance int32 [mscorlib]System.Collections.IList::Add(object)
19: IL_0012: pop
20: IL_0013: ldloc.0
21: IL_0014: ldstr "Verona"
22: IL_0019: callvirt instance int32 [mscorlib]System.Collections.IList::Add(object)
23: IL_001e: pop
24: IL_001f: ldloc.0
25: IL_0020: ldc.i4.0
26: IL_0021: callvirt instance object [mscorlib]System.Collections.IList::get_Item(int32)
27: IL_0026: castclass [mscorlib]System.String
28: IL_002b: stloc.1
29: IL_002c: nop
30: IL_002d: ldloc.0
Observe que na linha 27 existe uma instrunção castclass (que realiza o cast)
Agora veja o IL gerado no segundo exemplo (usando generics) :
1: .method public hidebysig static void Main() cil managed
2: {
3: .entrypoint
4: // Code size 47 (0x2f)
5: .maxstack 2
6: .locals init (class [mscorlib]System.Collections.Generic.IList`1<string> V_0,
7: string V_1)
8: IL_0000: nop
9: IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
10: IL_0006: stloc.0
11: IL_0007: ldloc.0
12: IL_0008: ldstr "Str1"
13: IL_000d: callvirt instance void class [mscorlib]System.Collections.Generic.ICollection`1<string>::Add(!0)
14: IL_0012: nop
15: IL_0013: ldloc.0
16: IL_0014: ldstr "Str1"
17: IL_0019: callvirt instance void class [mscorlib]System.Collections.Generic.ICollection`1<string>::Add(!0)
18: IL_001e: nop
19: IL_001f: ldloc.0
20: IL_0020: ldc.i4.0
21: IL_0021: callvirt instance !0 class [mscorlib]System.Collections.Generic.IList`1<string>::get_Item(int32)
22: IL_0026: stloc.1
23: IL_0027: ldloc.1
24: IL_0028: call void [mscorlib]System.Console::WriteLine(string)
25: IL_002d: nop
26: IL_002e: ret
27: } // end of method TestGenericCollection::Main
Note (linhas 21, 22) que o compilador não usou a instrução castclass, ou seja, nenhum cast foi realizado!
No próximo post pretendo falar um pouco mais sobre generics e apresentar um exemplo.
Até mais.
[+/-] mostrar/esconder este post