IL 101–Parte 10 (Delegates e eventos)

Publicado em 01/09/2010

3


Olá galera, tudo certin? Depois do desabafo de ontem, resolvi pegar “leve” comigo mesmo e pisar em terra conhecida: voltemos a boa (e já velha) série IL 101.

Go Code!

Recapitulando

Essa série tem por objetivo apresentar fundamentos da programação em Intermediate Language. O que já vimos até aqui foi:

Sobre essa parte

Hoje pretendo falar um poquin sobre Delegates e Eventos. Nada muito pesado, mas, para variar, com muitas revelações. Começo falando sobre os Delegates, depois falo sobre eventos.

Exemplo para Delegates

Sem mais delongas, nosso código IL de exemplo:


              
.assembly extern mscorlib {} .assembly demodelegates { .ver 1:0:1:0 } .module demodelegates.exe .namespace ILDemos { .class EntryPoint extends [mscorlib]System.Object { .method static void Main() cil managed { .maxstack 2 .locals init ( class [demodelegates] ILDemos.WriteTextMethod ) .entrypoint ldnull ldftn void [mscorlib] System.Console::WriteLine(string) newobj instance void ILDemos.WriteTextMethod:: .ctor(object, native int) stloc.0 ldloc.0 ldstr "Hello, Delegates World!!!" callvirt instance void ILDemos.WriteTextMethod:: Invoke(string) } } .class public auto ansi sealed WriteTextMethod extends [mscorlib]System.MulticastDelegate { .method public specialname rtspecialname instance void .ctor(object, native int) runtime managed { } .method public virtual instance void Invoke(string) runtime managed { } } }

              

    Nosso código de exemplo é simples, mas limpin Smiley piscando

    Salve este conteúdo em um arquivo chamado demodelegates.il e compile-o.

    Que venham as considerações…

    Criando um novo tipo de delegate: WriteTextMethod

    Você sabe o que acontece quando escreve algo assim em C#?

    
                  
    1 public delegate void WriteTextMethod(string value);
    
                  

    Então … Por baixo do capô (essa expressão ficou clássica, para mim), o compilador acaba escrevendo algo assim:

    
                  
    1 .class public auto ansi sealed WriteTextMethod 2 extends [mscorlib]System.MulticastDelegate 3 { 4 .method public specialname rtspecialname 5 instance void .ctor(object, native int) 6 runtime managed 7 { 8 } 9 10 .method public virtual instance void 11 Invoke(string) runtime managed 12 { 13 } 14 }
    
                  

    Sim, senhoras e senhores, nossos delegates são, por baixo do capô, classes. Mas não são classes tão normais. Vamos aos “fatos marcantes”:

    1. Para ser um delegate, a classe deve herdar, obrigatoriamente de System.MulticastDelegate;
    2. Um delegate não é autorizado a possuir atributos (variáveis de classe);
    3. Um delegate não é autorizado a conter qualquer método que não seja: Invoke, BeginInvoke, ou EndInvoke;
    4. Não é possível prover implementação para os métodos; A CLR irá fazer isso.

    Observe que os dois métodos declarados possuem os modificadores runtime managed (linhas 6 e 11). O runtime indica que o CLR sabe como implementar o código relacionado.

    Outro ponto que gostaria de destacar é o construtor. Ele recebe dois parâmetros:

    1. o primeiro, é o objeto que contém o método para o qual estamos apontando. Deve-se passar null para métodos estáticos
    2. o segundo, é um ponteiro para o ponto de entrada (entrypoint) do método que estamos “envelopando” no delegate.

    Usando um delegate

    Agora que já sabemos como criar um delegate (é uma classe com quase nenhum código), chega a hora de sabermos utilizar esse delegate. Se estivessemos programando em C#, escreveríamos algo assim:

    
                  
    1 WriteTextMethod del = new WriteTextMethod(Console.WriteLine); 2 del("Hello, World");
    
                  

    Por baixo do capô, essa sintaxe exótica ganha corpo. Observe:

    
                  
    1 ldnull 2 ldftn void [mscorlib] 3 System.Console::WriteLine(string) 4 newobj instance void 5 ILDemos.WriteTextMethod:: 6 .ctor(object, native int) 7 stloc.0 8 9 ldloc.0 10 ldstr "Hello, Delegates World!!!" 11 callvirt instance void ILDemos.WriteTextMethod:: 12 Invoke(string)
    
                  

    Para conhecimento, a instrução ldnull (linha 1) carrega um valor nulo no Evaluation Stack. ldtfn carrega o ponteiro para o entrypoint do método. Pela lógica, já sabemos que estamos carregando um método estático (por isso, estamos passando nulo).

    Observe que a execução do método ocorre mediante a execução do método Invoke.

    Agora, nosso exemplo para eventos

    Outra vez, sem delongas, nosso exemplo para eventos:

    
                  
    .assembly extern mscorlib {} .assembly demoevents { .ver 1:0:1:0 } .module demoevents.exe .namespace ILDemos { .class EntryPoint extends [mscorlib]System.Object { .method static void Main() cil managed { .entrypoint .maxstack 1 .locals init ( [0] class ILDemos.EventDemoClass ) newobj instance void ILDemos.EventDemoClass::.ctor() stloc.0 ldloc.0 ldnull ldftn void ILDemos.EntryPoint::p_MyEvent (object, class [mscorlib]System.EventArgs) newobj instance void[mscorlib] System.EventHandler::.ctor(object, native int) callvirt instance void ILDemos.EventDemoClass::add_MyEvent (class [mscorlib] System.EventHandler) ldloc.0 callvirt instance void ILDemos.EventDemoClass::RaiseMyEvent() ldloc.0 ldnull ldftn void ILDemos.EntryPoint::p_MyEvent (object, class [mscorlib]System.EventArgs) newobj instance void[mscorlib] System.EventHandler::.ctor(object, native int) callvirt instance void ILDemos.EventDemoClass::remove_MyEvent (class [mscorlib] System.EventHandler) ret } .method private hidebysig static void p_MyEvent ( object, class [mscorlib]System.EventArgs ) cil managed { .maxstack 1 ldstr "Dentro de p_MyEvent..." call void [mscorlib]System.Console ::WriteLine(string) } } .class public auto ansi sealed EventDemoClass extends [mscorlib]System.Object { .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 1 ldarg.0 call instance void [mscorlib]System.Object::.ctor() ret } .field private class [mscorlib]System.EventHandler MyEvent .event [mscorlib]System.EventHandler MyEvent { .addon instance void ILDemos.EventDemoClass:: add_MyEvent(class [mscorlib]System.EventHandler) .removeon instance void ILDemos.EventDemoClass:: remove_MyEvent(class [mscorlib]System.EventHandler) } .method public hidebysig specialname instance void add_MyEvent(class [mscorlib]System.EventHandler) { .maxstack 4 ldstr "Assinando MyEvent..." call void [mscorlib]System.Console ::WriteLine(string) ldarg.0 dup ldfld class [mscorlib]System.EventHandler ILDemos.EventDemoClass::MyEvent ldarg.1 call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine( class [mscorlib]System.Delegate, class [mscorlib]System.Delegate ) castclass class [mscorlib]System.EventHandler stfld class [mscorlib]System.EventHandler ILDemos.EventDemoClass::MyEvent ret } .method public hidebysig specialname instance void remove_MyEvent(class [mscorlib]System.EventHandler) { .maxstack 4 ldstr "Desassinando MyEvent..." call void [mscorlib]System.Console ::WriteLine(string) ldarg.0 dup ldfld class [mscorlib]System.EventHandler ILDemos.EventDemoClass::MyEvent ldarg.1 call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove( class [mscorlib]System.Delegate, class [mscorlib]System.Delegate ) castclass [mscorlib]System.EventHandler stfld class [mscorlib]System.EventHandler ILDemos.EventDemoClass::MyEvent ret } .method public hidebysig instance void RaiseMyEvent() cil managed { .maxstack 4 ldarg.0 ldfld class [mscorlib]System.EventHandler ILDemos.EventDemoClass::MyEvent ldnull ceq brtrue done ldarg.0 ldfld class [mscorlib]System.EventHandler ILDemos.EventDemoClass::MyEvent ldarg.0 newobj instance void [mscorlib]System.EventArgs::.ctor() callvirt instance void [mscorlib]System.EventHandler:: Invoke(object, class [mscorlib]System.EventArgs) done: ret } } }
    
                  

    Outra vez, nosso código de exemplo é simples, mas limpin Smiley piscando

    Salve este conteúdo em um arquivo chamado demoevents.il e compile-o.

    Que venham as considerações…

    Anatomia de um evento

    Essa talvez seja a parte mais “surpreendente” de hoje. Afinal, o que é, de verdade, um evento? Caros … um evento é só uma “decoração” para um delegate.

    Um código assim, em C# …

    
                  
    1 public event EventHandler MyEvent
    
                  

    gera algo assim em Intermediate Language:

    
                  
    1 .field private class 2 [mscorlib]System.EventHandler MyEvent 3 4 .event [mscorlib]System.EventHandler MyEvent 5 { 6 .addon instance void ILDemos.EventDemoClass:: 7 add_MyEvent(class [mscorlib]System.EventHandler) 8 9 .removeon instance void ILDemos.EventDemoClass:: 10 remove_MyEvent(class [mscorlib]System.EventHandler) 11 } 12 13 .method public hidebysig specialname instance void 14 add_MyEvent(class [mscorlib]System.EventHandler) 15 { 16 ... 17 ret 18 } 19 20 .method public hidebysig specialname instance void 21 remove_MyEvent(class [mscorlib]System.EventHandler) 22 { 23 ... 24 ret 25 }
    
                  

    Considerando:

    1. como acontece com atributos e propriedades , o compilador “escreve” para nós um atributo, automaticamente, para suportar o evento. Esse atibuto é tipado com o delegate;
    2. como acontece com atributos e propriedades , embora exista um “marcador” para o evento, dois métodos de apoio são escritos para fazer a ação real (um add_* e o outro remove_*);
    3. é nos métodos de apoio que a “ação real” acontece;

    Uma coisa que talvez você não saiba, é que pode ter mais controle sobre o código que o compilador produz. No lugar de deixar o compilador definir o atributo que conterá o delegate, pode especificar isso, da mesma forma que pode regrar as operações Add e Remove. O fragmento de código que segue, é válido em C#:

    
                  
    EventHandler evh; public event EventHandler MyEvent2 { add { evh += value; } remove { evh -= value; } }
    
                  

    Escrevendo o método Add para eventos

    Observe o código IL:

    
                  
    1 .method public hidebysig specialname instance void 2 add_MyEvent(class [mscorlib]System.EventHandler) 3 { 4 .maxstack 4 5 6 ldstr "Assinando MyEvent..." 7 call void [mscorlib]System.Console 8 ::WriteLine(string) 9 10 ldarg.0 11 dup 12 ldfld class [mscorlib]System.EventHandler 13 ILDemos.EventDemoClass::MyEvent 14 ldarg.1 15 16 call class [mscorlib]System.Delegate 17 [mscorlib]System.Delegate::Combine( 18 class [mscorlib]System.Delegate, 19 class [mscorlib]System.Delegate 20 ) 21 22 23 castclass class [mscorlib]System.EventHandler 24 stfld class [mscorlib]System.EventHandler 25 ILDemos.EventDemoClass::MyEvent 26 ret 27 }
    
                  

    O método é simples. Por diversão, adicionei a impressão de uma mensagem na tela sempre que o add para o evento for acionado. Repare que, na prática, o que esse método faz é combinar  o delegate recebido por parâmetro com o que está no “field”. Para isso, o método estático Combine, da classe delegate é utilizado.

    Escrevendo o métdo Remove para eventos

    Observe o código IL:

    
                  
    1 .method public hidebysig specialname instance void 2 remove_MyEvent(class [mscorlib]System.EventHandler) 3 { 4 .maxstack 4 5 6 ldstr "Desassinando MyEvent..." 7 call void [mscorlib]System.Console 8 ::WriteLine(string) 9 10 11 ldarg.0 12 dup 13 ldfld class [mscorlib]System.EventHandler 14 ILDemos.EventDemoClass::MyEvent 15 ldarg.1 16 17 call class [mscorlib]System.Delegate 18 [mscorlib]System.Delegate::Remove( 19 class [mscorlib]System.Delegate, 20 class [mscorlib]System.Delegate 21 ) 22 23 castclass [mscorlib]System.EventHandler 24 stfld class [mscorlib]System.EventHandler 25 ILDemos.EventDemoClass::MyEvent 26 ret 27 }
    
                  

    Outra vez, o método é simples. Por diversão, adicionei a impressão de uma mensagem na tela sempre que o remove para o evento for acionado. Repare que, na prática, o que esse método faz é Remover o delegate recebido por parâmetro com o que está no “field”. Para isso, o método estático Combine, da classe delegate é utilizado.

    Disparando um evento

    O código que segue, em C#, é bem comum em nosso dia-a-dia:

    
                  
    1 public void RaiseMyEvent() 2 { 3 if (this.MyEvent != null) 4 this.MyEvent(this, new EventArgs()); 5 }
    
                  

    Aqui, a versão em Intermediate Language:

    
                  
    1 .method public hidebysig instance 2 void RaiseMyEvent() cil managed 3 { 4 5 .maxstack 4 6 7 ldarg.0 8 ldfld class [mscorlib]System.EventHandler 9 ILDemos.EventDemoClass::MyEvent 10 ldnull 11 ceq 12 brtrue done 13 14 ldarg.0 15 ldfld class [mscorlib]System.EventHandler 16 ILDemos.EventDemoClass::MyEvent 17 ldarg.0 18 newobj instance void 19 [mscorlib]System.EventArgs::.ctor() 20 21 callvirt instance void [mscorlib]System.EventHandler:: 22 Invoke(object, class [mscorlib]System.EventArgs) 23 24 done: 25 ret 26 }
    
                  

    Repare que, por baixo do capô, o acionamento do método não utiliza o “evento” e sim o atributo que armazena o Delegate. Observe que a “chamada” ocorre mediante o método Invoke.

    Assinando o evento

    A assinatura de evento, em C#, é feita dessa forma:

    
                  
    1 p.MyEvent += new EventHandler(p_MyEvent);
    
                  

    Traduzindo para IL:

    
                  
    1 ldloc.0 2 ldnull 3 ldftn void ILDemos.EntryPoint::p_MyEvent 4 (object, class [mscorlib]System.EventArgs) 5 newobj instance void[mscorlib] 6 System.EventHandler::.ctor(object, native int) 7 8 callvirt instance void ILDemos.EventDemoClass::add_MyEvent 9 (class [mscorlib] System.EventHandler)
    
                  

    Observe que assumo que o objeto que serve o evento está em loc.0. Também perceba que crio uma instância do Delegate associado ao evento e passo essa instância diretamente para o acessor Add.

    Desassinando o evento

    Para desassinar um evento, em C#, escrevemos iso:

    
                  
    1 p.MyEvent += new EventHandler(p_MyEvent);
    
                  

    Traduzindo para IL:

    
                  
    1 ldloc.0 2 ldnull 3 ldftn void ILDemos.EntryPoint::p_MyEvent 4 (object, class [mscorlib]System.EventArgs) 5 newobj instance void[mscorlib] 6 System.EventHandler::.ctor(object, native int) 7 8 callvirt instance void ILDemos.EventDemoClass::remove_MyEvent 9 (class [mscorlib] System.EventHandler)
    
                  

    Mesmas observações da assinatura, cabem aqui!

    Bem pessoal, por hoje, é isso Smiley piscando

    Publicado em: Post