Olá pessoal, tudo certo?
No post anterior iniciamos a construção de um projeto open-source que objetiva facilitar escrita de código para Emitting. O projeto se constitu, basicamente, de uma DSL que permite a escrita de código menos verboso e em nível mais alto (se comparado com a biblioteca de Emitting do .NET Framework).
Hoje, continuamos esse projeto adicionando alguns facilitadores para emissão de desvios condicionais. Se você não “entende” como funcionam desvios condicionais em Intermediate Language (que é a “linguagem” utilizada nos processos de emitting), considere a leitura da parte 4 de minha série sobre IL.
Geração de código executável on-the-fly, usando emitting, pode, em alguns cenários, ser uma alternativa atraente para gerar código com desempenho e flexibilidade incomparáveis.
Como estamos falando de três assuntos “densos” (IL, Emitting e DSLs), o código e o post acabam ficando, consequentemente, “densos” também. Em caso de dúvidas, me contate, ok!?
Lembre-se que este é um projeto open-source. Considere pegar o código fonte desse projeto em https://github.com/ElemarJR/FluentIL.
Desvios condicionais em IL usando “If..Goto”
Implementar desvios condicionais em IL não é exatamente uma operação trivial (assim como ocorre com loops). Isso ocorre porque IL não tem suporte nativo a comparações “If..Else..Endif”, mas sim “If..Goto”.
Considere o seguinte código em C#:
1 public static string Dummy(int a, int b) 2 { 3 if (a > b) 4 return "Yes"; 5 else 6 return "No"; 7 }
Agora, observe o mesmo código escrito em IL (já utilizando uma versão “aumentada” de nosso pequeno “toolkit”).
1 [Test] 2 public void TwoNumbersAreEquals_TrueReturnsYesFalseReturnsNo_Reference() 3 { 4 var dm = IL.NewMethod(typeof(string), typeof(int), typeof(int)) 5 .Ldarg(0, 1) 6 .Beq("if_true") 7 .Ldstr("No") 8 .Br("done") 9 .MarkLabel("if_true") 10 .Ldstr("Yes") 11 .MarkLabel("done") 12 .Ret(); 13 14 dm.Invoke(2, 2).Should().Be("Yes"); 15 dm.Invoke(2, 3).Should().Be("No"); 16 }
Ou ainda…
1 [Test] 2 public void TwoNumbersAreEquals_TrueReturnsYesFalseReturnsNo_Reference2() 3 { 4 var dm = IL.NewMethod(typeof(string), typeof(int), typeof(int)) 5 .Ldarg(0, 1) 6 .Ceq() 7 .Brfalse("if_false") 8 .Ldstr("Yes") 9 .Br("done") 10 .MarkLabel("if_false") 11 .Ldstr("No") 12 .MarkLabel("done") 13 .Ret(); 14 15 dm.Invoke(2, 2).Should().Be("Yes"); 16 dm.Invoke(2, 3).Should().Be("No"); 17 }
Repare que, mesmo utilizando uma interface fluente, o programador precisa estar familiriarizado com o conceito de “If .. Goto” (sentindo saudades do BASIC?!), bem menos intuitivo que o “If..Else..Endif” do C#.
Planejando um “If..Else..EndIf” da DSL de Emitting
Se IL não tem suporte nativo para “If..Else..EndIf”, implementamos o nosso! Visando facilitar as coisas, incrementei a nossa DSL com algumas novas instruções (If..Else..Endif). Observe como a mesma comparação fica mais fácil de escrever:
1 [Test] 2 public void TwoNumbersAreEquals_TrueReturnsYesFalseReturnsNo() 3 { 4 var dm = IL.NewMethod(typeof(string), typeof(int), typeof(int)) 5 .Ldarg(0, 1) 6 .Ifeq() 7 .Ldstr("Yes") 8 .Else() 9 .Ldstr("No") 10 .EndIf() 11 .Ret(); 12 13 dm.Invoke(2, 2).Should().Be("Yes"); 14 dm.Invoke(2, 3).Should().Be("No"); 15 }
Bem melhor, não acha? Sem labels, sem desvios.
![]()
Implementando o “suporte” a desvios condicionais “If..Goto” em nossa DSL
Como indiquei na parte 4 de minha série sobre IL, Intermediate Language oferece instruções distintas para cada tipo de comparação. Manter a “familiaridade” com Intermediate Language implicar na implementação de diversos métodos muito parecidos onde o que mudaria seria apenas o OpCode sendo “emitido”. Por isso, visando diminuir erros, recorri a um pouco de T4 para essa atividade. Observe:
1 public partial class DynamicMethodBody 2 { 3 <# string [] opcodes = new string [] { 4 "Beq", "Beq_S", 5 "Bne_Un", "Bne_Un_S", 6 "Bge", "Bge_S", "Bge_Un", "Bge_Un_S", 7 "Bgt", "Bgt_S", "Bgt_Un", "Bgt_Un_S", 8 "Ble", "Ble_S", "Ble_Un", "Ble_Un_S", 9 "Blt", "Blt_S", "Blt_Un", "Blt_Un_S", 10 "Brtrue", "Brfalse", 11 "Br", "Br_S" }; 12 foreach (var opcode in opcodes) 13 { 14 #> 15 #region <#= opcode #> 16 public DynamicMethodBody <#= opcode #>(string label) 17 { 18 return <#= opcode #>(GetLabel(label)); 19 } 20 21 public DynamicMethodBody <#= opcode #>(Label label) 22 { 23 return Emit(OpCodes.<#= opcode #>, label); 24 } 25 #endregion 26 }
Perceba que:
- tornei DynamicMethodBody uma classe parcial, permitindo que eu escrevesse um arquivo T4 (extensão .tt) para automatizar a geração dos diversos métodos;
- utilizei um array de strings com o nome de todas as instruções IL suportadas pela biblioteca de emitting;
- para cada Opcode gero duas sobrecargas de método. Uma recebendo o label da posição destino (desvio) e outra recebendo uma string (aproveitando o “simplificador de uso” de labels apresentado no post anterior).
Se você ainda não sabe T4, trate de aprender (código mecânico [quase idêntico, com pequenas modificações], para mim, ferem o conceito DRY. São débitos técnicos, aceite-os, mas pague-os em seus projetos!).
Implementando o “suporte” a desvios condicionais “If..Else..EndIf” em nossa DSL
Em IL, cada instrução opera de forma intependente das outras. Sua relação é sempre com o contexto (valores na pilha e no Heap). Instruções “If..Else..Endif” não seguem esse princípio, são dependentes.
Implemento o suporte a “If..Else..EndIf” partindo do segundo exemplo desse post para “If..Goto”. Para operacionalizar isso em nossa DSL criei uma nova entidade: IfEmitter. Observe:
1 class IfEmitter 2 { 3 readonly DynamicMethodBody _Generator; 4 readonly string _IfFalse; 5 readonly string _Done; 6 bool _WithElse; 7 8 public IfEmitter(DynamicMethodBody generator) 9 { 10 _Generator = generator; 11 _IfFalse = string.Format("IfFalse_{0}", Guid.NewGuid()); 12 _Done = string.Format("IfFalse_{0}", Guid.NewGuid()); 13 } 14 15 public void EmitIf(OpCode comparasionOpcode) 16 { 17 _Generator 18 .Emit(comparasionOpcode) 19 .Brfalse(_IfFalse); 20 } 21 22 public void EmitElse() 23 { 24 _Generator 25 .Br(_Done) 26 .MarkLabel(_IfFalse); 27 28 _WithElse = true; 29 } 30 31 public void EmitEndIf() 32 { 33 if (!_WithElse) _Generator.MarkLabel(_IfFalse); 34 _Generator.MarkLabel(_Done); 35 } 36 }
Essa entidade mantem e controla algumas condições de estado importantes para implementação do “If..Else..Endif”. Como pode perceber, mantenho identificadores para o desvio para condição “falso” e para “pronto”. Além disso, condiciono o emit do EndIf a presença do Else.
A implementação dos “comandos” em nossa DSL foi feita assim:
1 public partial class DynamicMethodBody 2 { 3 readonly Stack<IfEmitter> _IfEmitters = new Stack<IfEmitter>(); 4 public DynamicMethodBody Else() 5 { 6 _IfEmitters.Peek().EmitElse(); 7 return this; 8 } 9 10 public DynamicMethodBody EndIf() 11 { 12 _IfEmitters.Pop().EmitEndIf(); 13 return this; 14 } 15 16 <# string [] opcodes = new string [] { 17 "Beq", "Beq_S", 18 "Bne_Un", "Bne_Un_S", 19 "Bge", "Bge_S", "Bge_Un", "Bge_Un_S", 20 "Bgt", "Bgt_S", "Bgt_Un", "Bgt_Un_S", 21 "Ble", "Ble_S", "Ble_Un", "Ble_Un_S", 22 "Blt", "Blt_S", "Blt_Un", "Blt_Un_S", 23 "Brtrue", "Brfalse", 24 "Br", "Br_S" }; 25 foreach (var opcode in opcodes) 26 { 27 #> 28 #region <#= opcode #> 29 <# 30 if (!opcode.StartsWith("Br") && 31 !opcode.StartsWith("Bge") && !opcode.EndsWith("_S") 32 && !opcode.Equals("Ble") && !opcode.Equals("Ble_Un") 33 && !opcode.Equals("Bne_Un") 34 ) { #> 35 public DynamicMethodBody C<#= opcode.Substring(1) #>() 36 { 37 return Emit(OpCodes.C<#= opcode.Substring(1) #>); 38 } 39 40 public DynamicMethodBody If<#= opcode.Substring(1) #>() 41 { 42 var emitter = new IfEmitter(this); 43 _IfEmitters.Push(emitter); 44 emitter.EmitIf(OpCodes.C<#= opcode.Substring(1) #>); 45 return this; 46 } 47 <# }#> 48 #endregion 49 50 <# 51 } 52 #> 53 }
Perceba que esse fragmento de T4 foi extraído do mesmo código usado para gerar o suporte para “If..Goto”. A geração dos diversos “comandos” IfXXX está condicionada a disponibilidade de instruções de comparação simples. Observe também o empilhamento de “IfEmitters” para permitir a utilização de If’s aninhados
Por hoje, era isso.
![]()
No próximo post, implementamos o Switch (o que acham?!)






Lucas
09/02/2011
Elemar.. admiro muito seu conhecimento em .NET. Realmente me parece ser advanced.
Mas sinceramente.. qual a vantagem em aprender IL?? Quem vive de programar em IL além da MS?
Desculpe.. mas não faz sentido aprender IL, sinceramente.
elemarjr
09/02/2011
Olá Lucas, tudo certo?
Antes de qualquer coisa, obrigado por opinar.
Tenho verdadeira paixão por saber como as coisas funcionam e, por isso, resolvi escrever sobre os “bastidores” do .net
Não sei qual é o seu cenário de desenvolvimento. Sabendo IL, consegui atingir resultados muito positivos mediante o desenvolvimento de código on-the-fly. Há uma série no blog sobre image processing onde deixo tudo isso evidente.
Além disso, sempre gostei de desenvolver software de base. Sabendo IL desenvolvi conhecimento para escrita de frameworks de isolamento (similares ao moq, por exemplo).
Escrever sobre il, para mim, é um prazer. Meu blog também trata de outros temas. Gosto do conteúdo que escrevo e pretendo escrever sobre muitos outros,
Só uma dica, não é porque você não vê utilidade em um conteúdo que ninguém mais verá.
travel
09/02/2011
Depende muito de onde se trabalha, pessoal que trabalha com P&D acaba se deparando com essas situações. Se você está fazendo algo que precise de mais performance ou criação dinâmica de código pode ser muito útil.
Juan Lopes
09/02/2011
Lucas, qual a diferença entre você e uma pessoa que trabalha na Google?
Se você só tentar aprender aquilo que você precisa para usar agora, vai passar a vida inteira fazendo a mesma coisa.
Te garanto que na Google as pessoas não vivem de fazer CRUDzinho e, em termos de importância, saber IL é muito superior a saber popular um GridView.
Qual a diferença entre o Elemar e você?
—
Elemar, keep the good work.
elemarjr
09/02/2011
A propósito, aqui mesmo no blog você encontra PowerShell, Html5, Arquitetura de Software, C++, C#, MSBuild…
elemarjr
09/02/2011
Windows Azure, IA, Gestão de Carreira, Paralelismo, REST ..
De tempos em tempos .. IL
Rodrigo Vidal
10/02/2011
O Juan disse exatamente o que costumo dizer para quem me faz essa pergunta.
Elemar como eu ja havia te dito em private. Obra primorosa, o FluentIL.
Parabens amigo!
Rodrigo Vidal
10/02/2011
Adicional: Lucas diz “Elemar.. admiro muito seu conhecimento em .NET. Realmente me parece ser advanced.”
Me parece ser? Ta de brincadeira né? rs
Desculpa mas não pude deixar de comentar.
Israel Aece
10/02/2011
Boas Lucas,
Um exemplo de uso de IL é feito quando você cria uma RegularExpression (RegEx), e define a opção Compiled através do enumerador RegexOptions. Isso faz com que a sua expressão seja compilada e, consequentemente, ter uma melhor performance quando executada. Lá usa-se IL, em uma técnica bem próxima a qual o Elemar detalha aqui.