Recapitulando
Essa série tem por objetivo os apresentar fundamentos da programação em Intermediate Language. O que já vimos até aqui foi:
- Parte 7: Atributos e propriedades
- Parte 6: Variáveis locais e loops
- Parte 5: Trabalhando com parâmetros
- Parte 4: Classes e namespaces
- Parte 3: Equilíbrio Performance e Memória em IL
- Parte 2: Estrutura da máquina virtual IL
- Parte 1: Apresentando IL
Sobre essa parte
Agora que já sabemos como criar tipos com atributos e propriedades (parte 7), vamos avançar. Nessa parte, irei demonstrar como a CLR trata a inicialização de objetos, com ênfase nos métodos construtores. Para tornar as coisas mais interessantes, vou continuar o exemplo que iniciei na parte anterior. Adicionaremos dois construtores: um sem parâmetros (default) que inicia a propriedade ChildCount para 1; e outro que recebe um unsigned int8 definindo o valor inicial dessa propriedade.
Por enquanto vamos focar em construtores de instância. Não tratarei, agora, de construtores estáticos.
Construtores
Podemos fazer algumas observações comuns ao funcionamento de construtores, sejam em value types como reference types:
- Construtores de instância são métodos que sempre tem o nome .ctor e retornam void (por curiosidade, construtores estáticos são nomeados como .cctor);
- São sempre decorados com dois modificadores – specialname e rtspecialname;
- Em Intermediate Language, construtores funcionam como métodos normais que podem ser chamados a qualquer momento (Lembrando: isso não é uma prática muito bonita);
- Podem ser definidos tantas sobrecargas para esse método quanto necessário;
- Para reference types, o construtor sempre deverá chamar o construtor da classe base. Não fazer isso cria o risco de que algum atributo não seja devidamente inicializado. Não há qualquer exigência sobre o momento que essa evocação deve ocorrer, mas costuma ser considerado um bom padrão fazer isso em primeiro lugar;
- Para value types, não é necessário evocar o construtor da base, mesmo porque não há atributos por inicializar;
- Não é possível definir um construtor como virtual;
- Construtores para refence types são sempre evocados por default. Por outro lado, construtores de value types nunca são evocados (apenas se acionados diretamente);
- Linguagens de alto nível impedem a associação de um tipo de retorno para o construtor. Em Intermediate Language construtores precisam ser declaros explicitamente como retornando void;
- Algumas linguagens de alto nível, como C#, não permitem a definição de construtores sem parâmetros para value types. Em Intermediate Language não há restrição sintática para isso;
- Compiladores de linguagens de alto nível inserem código IL nos construtores de reference types para chamar o construtor da classe base; IL não faz isso.
Criando um construtor default
Chega de teoria, agora vamos para o código ![]()
Que fique claro: construir construtores sem argumentos em value types não é uma boa prática. Entretanto, nesse primeiro exemplo, por simplicidade, será exatamente isso que faremos. Observe o código que segue:
.assembly Family2
{
.ver 1:0:1:0
}
.module Family2.dll
.namespace ElemarJR.Blog
{
.class public ansi auto sealed FamilySample
extends [mscorlib]System.ValueType
{
.field private unsigned int8 _childCount
.method public specialname rtspecialname instance void .ctor()
{
ldarg.0
ldc.i4.s 1
stfld unsigned int8
ElemarJR.Blog.FamilySample::_childCount
ret
}
.method specialname public instance unsigned int8 get_ChildCount()
cil managed
{
ldarg.0
ldfld unsigned int8 ElemarJR.Blog.FamilySample::_childCount
ret
}
.method specialname public instance void set_ChildCount(unsigned int8)
cil managed
{
ldarg.0
ldarg.1
stfld unsigned int8 ElemarJR.Blog.FamilySample::_childCount
ret
}
.property instance unsigned int8 ChildCount()
{
.get instance unsigned int8 ElemarJR.Blog.FamilySample::get_ChildCount()
.set instance void ElemarJR.Blog.FamilySample::set_ChildCount(unsigned int8)
}
}
}
O trecho em negrito mostra o construtor. Simples, não? Repare que é um método normal que inicializa o atributo. (Se desejar, salve esse código em um arquivo chamado Family2.il e compile-o como DLL)
Agora, um exemplo de código cliente, em IL para esse value type:
.assembly extern Family2
{
.ver 1:0:1:0
}
.assembly TestFamily2
{
.ver 1:0:1:0
}
.module TestFamily2.exe
.namespace ElemarJR.Blog
{
.class EntryPoint
extends [mscorlib]System.Object
{
.method static void Main() cil managed
{
.maxstack 2
.locals init (
valuetype [Family2]ElemarJR.Blog.FamilySample
)
.entrypoint
ldloca.s 0
call instance void [Family2]ElemarJR.Blog.FamilySample::
.ctor()
ldstr "ChildCount is " call void [mscorlib]System.Console::Write(string) ldloca.s 0 call instance unsigned int8 [Family2] ElemarJR.Blog.FamilySample::get_ChildCount() call void [mscorlib]System.Console::WriteLine(int32) ret } } }
O trecho de código em negrito chama o construtor do value type explicitamente. (Se desejar, salve esse código em um arquivo chamado TestFamily2.il e compile-o como executável)
Adicionando um construtor que aceita parâmetros
Agora que sabemos como escrever construtores sem parâmetros, vamos escrever um que aceita um parâmetro determinando o valor inicial de um atributo. Considere o exemplo anterior, adicionando o seguinte método:
.method public specialname rtspecialname instance void .ctor
(unsigned int8)
{
ldarg.0
ldarg.1
stfld unsigned int8
ElemarJR.Blog.FamilySample::_childCount
ret
}
Adicionando este código, temos todo o necessário para termos dois construtores. Entretanto, observe que os dois inicializam, independentemente um atributo. Uma boa prática seria fazer um construtor chamar o outro. Assim, vamos modificar o primeiro construtor:
.method public specialname rtspecialname instance void .ctor()
{
ldarg.0
ldc.i4.s 1
stfld unsigned int8
ElemarJR.Blog.FamilySample::_childCount
call instance void ElemarJR.Blog.FamilySample::
.ctor(unsigned int8)
ret }
O trecho em negrito destaca a chamada do outro construtor. O trecho riscado deverá ser removido do código. (Se está testando, recompile tudo).
Trabalhando com Reference Objects
Agora que já conseguimos instanciar e inicializar value types é hora de aprendermos a lidar com Reference types. Em Intermediate Language, fazemos isso derivando a classe de System.Object. Quanto ao construtor, passamos a ter a responsabilidade de chamar o construtor do tipo base. Nossa implementação agora fica mais ou menos assim:
.assembly Family2
{
.ver 1:0:1:0
}
.module Family2.dll
.namespace ElemarJR.Blog
{
.class public ansi auto sealed FamilySample
extends [mscorlib]System.Object
{
.field private unsigned int8 _childCount
.method public specialname rtspecialname instance void .ctor()
{
ldarg.0
ldc.i4.s 1
call instance void ElemarJR.Blog.FamilySample::
.ctor(unsigned int8)
ret
}
.method public specialname rtspecialname instance void .ctor
(unsigned int8)
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ldarg.0
ldarg.1
stfld unsigned int8
ElemarJR.Blog.FamilySample::_childCount
ret
}
.method specialname public instance unsigned int8 get_ChildCount()
cil managed
{
ldarg.0
ldfld unsigned int8 ElemarJR.Blog.FamilySample::_childCount
ret
}
.method specialname public instance void set_ChildCount(unsigned int8)
cil managed
{
ldarg.0
ldarg.1
stfld unsigned int8 ElemarJR.Blog.FamilySample::_childCount
ret
}
.property instance unsigned int8 ChildCount()
{
.get instance unsigned int8 ElemarJR.Blog.FamilySample::get_ChildCount()
.set instance void ElemarJR.Blog.FamilySample::set_ChildCount(unsigned int8)
}
}
}
Os trechos em negrito destacam as mudanças: a classe herda de System.Object e os construtores chamam, direta ou indiretamente, o construtor da classe base.
Para nossos exemplos continuarem funcinando, teremos que alterar também o código cliente:
.assembly extern Family2
{
.ver 1:0:1:0
}
.assembly TestFamily2
{
.ver 1:0:1:0
}
.module TestFamily2.exe
.namespace ElemarJR.Blog
{
.class EntryPoint
extends [mscorlib]System.Object
{
.method static void Main() cil managed
{
.maxstack 2
.locals init (
class [Family2]ElemarJR.Blog.FamilySample
) .entrypoint
newobj void [Family2]ElemarJR.Blog.FamilySample::.ctor()
stloc.0
ldstr "ChildCount is " call void [mscorlib]System.Console::Write(string)
ldloc.s 0
call instance unsigned int8 [Family2] ElemarJR.Blog.FamilySample::get_ChildCount() call void [mscorlib]System.Console::WriteLine(int32) ret } } }
Os trechos em negrito indicam os pontos que foram alterados. Primeiro, observe que o modificador class foi utilizado no lugar de valuetype. Depois, usamos a instrução newobj para criar uma instância do tipo especificado. Por fim, voltamos usar as instruções normais para armazenar e recuperar valores dos atributos locais.
Conclusões
Hoje mostrei os procedimentos para a criação de construtores. Como visto, o compilador do C# simplifica bastante o trabalho com construtores, seja impedindo a definição de construtores sem parâmetros para value type, seja pela inclusão automática de código de chamada para o construtor da classe base.
Bem, por hoje é isso!






setembro 1st, 2010 → 0:01
[...] Parte 8: Construtores para ValueTypes e ReferenceTypes [...]
setembro 20th, 2010 → 0:37
[...] Parte 8: Construtores para ValueTypes e ReferenceTypes [...]