Olá pessoa, tudo certo?
Há algum tempo venho tratando de IL aqui no blog. Acredito no potencial da tecnologia e percebo grande utilidade, principalmente quando combinada com Emitting.
A utilização de IL e Emitting permite gerar código on-the-fly com performance superior.
Ao meu ver, há dois obstáculos comuns para quem está começando a utilizar IL com Emitting:
- Aprender IL, afinal é uma nova linguagem em nível mais baixo. (Isso pode ser “minimizado” aqui mesmo no blog (para quem não sabe, escrevi uma série inteira sobre o assunto);
- É difícil ser objetivo usando a biblioteca padrão de emitting do .net framework, As bibliotecas de Emitting do .NET são muito “verbosas” e não colaboram com a escrita de código.
Este é o post de apresentação de um toolkit opensource que desenvolveremos. O objetivo desse toolkit é tornar o processo de emitting mais simples com código mais legível.
Nesse post, vou apresentar, em um primeiro momento, algumas possibilidades de otimização (através dos testes que escrevi para o toolkit). Depois, mostro alguns detalhes de implementação.
Sobre os testes nesse projeto
Para esse projeto, demonstro uma prática que costumo adotar quando estou criando “interfaces” para código legado em que não possuo acesso aos fontes: Escrever métodos de teste para alguns cenários para perceber algumas possibilidades de melhoria.
Como isso funciona?
- Escrevo o melhor código que consigo para um determinado objetivo;
- Analiso criticamente as características do código que não estão me agradando;
- Escrevo algum código que melhore a interface com a biblioteca que estou utilizando / desenvolvendo;
- Escrevo um novo teste “utilizando” as melhorias.
A primeira parte desse post ilustra bem esse processo.
Melhorando a interface de Emitting do .NET Framework
Entendendo o problema…
Vamos começar de forma bem simples. Vamos usar emitting para gerar um método que soma dois inteiros passados por parâmetro.
1 public void TwoPlusTwoWithParameters_Reference() 2 { 3 // arrange 4 DynamicMethod dm = new DynamicMethod("SomeName", typeof(int), 5 new Type[] { typeof(int), typeof(int) }); 6 7 var ilgen = dm.GetILGenerator(); 8 ilgen.Emit(OpCodes.Ldarg_0); 9 ilgen.Emit(OpCodes.Ldarg_1); 10 ilgen.Emit(OpCodes.Add); 11 ilgen.Emit(OpCodes.Ret); 12 13 // act 14 var result = dm.Invoke(null, new object[] { 2, 2 }); 15 16 // assert 17 result.Should().Be(4); 18 }
Observe como o código é “complexo” e verboso. Começando pelo construtor do objeto DynamicMethod, considere:
- Se estamos criando um método dinâmico, por que especificar um nome para esse método?
- Não é o caso desse cenário, mas observe como é necessário informar o tipo de retorno (mesmo que o método seja void) e o tipo dos parâmetros (mesmo que nenhum seja necessário).
Outro problema visível está no método Emit. Existem diversas sobrecargas que não fazem nenhum tipo de consistência entre o opcode e eventuais parâmetros complementares.
Por fim, observe o Invoke. Novamente, um objeto host é obrigatório. Chato!
Veja a minha primeira proposta:
1 [Test] 2 public void TwoPlusTwoWithParameters_2() 3 { 4 // arrange 5 var dm = IL.NewMethod(typeof(int), typeof(int), typeof(int)) 6 .Ldarg(0, 1) 7 .Add() 8 .Ret(); 9 10 // act 11 var result = dm.Invoke(2, 2); 12 13 // assert 14 result.Should().Be(4); 15 }
O que fizemos?
Simplifiquei a criação do método. Nada de exigir um nome de método que não utilizo. Os tipos dos parâmetros são passados via “paramarray”. Além disso, adicionei suporte fluente para IL com métodos fortemente nomeados.
Observe que minha versão de Ldarg suporta a “carga” dos dois parâmetros para a pilha com uma única instrução (Salve params).
Agora… que tal trabalhar com parâmetros nomeados?! Observe:
1 [Test] 2 public void TwoPlusTwoWithNamedParameters() 3 { 4 // arrange 5 var dm = IL.NewMethod() 6 .WithParameter(typeof(int), "a") 7 .WithParameter(typeof(int), "b") 8 .Returns(typeof(int)) 9 10 .Ldarg("a", "b") 11 .Add() 12 .Ret(); 13 14 // act 15 var result = dm.Invoke(2, 2); 16 17 // assert 18 result.Should().Be(4); 19 }
Expandi a sintaxe para poder associar nomes a parâmetros durante o emitting. Ficou bacana, não?!
Com algum suporte a constantes
IL tem instruções definidas para otimizar processamentos comuns. A carga de valores constantes é um dos “alvos” de otimização do IL. Instruções específicas reduzem o tamanho dos arquivos binários do .NET e otimizam processamento. Infelizmente, esse número maior de instruções geram códigos como o que segue:
1 [Test] 2 public void TwoPlusTwo_Reference() 3 { 4 // arrange 5 var dm = IL.NewMethod(typeof(int)) 6 .Emit(OpCodes.Ldc_I4_2) 7 .Emit(OpCodes.Ldc_I4_2) 8 .Add() 9 .Ret(); 10 11 // act 12 var result = dm.Invoke(); 13 14 // assert 15 result.Should().Be(4); 16 }
Nesse método, que soma dois inteiros (2+2), utilizo instrução específica para carregar a constante (2) na pilha. Tudo bem IL definir instruções especiais como as apresentadas no código, entretanto, não há razões (ao meu ver), para forçar o desenvolvedor de emitting a ter que considerar essas instruções.
Observe no código que segue como reduzo a complexidade passando o valor que desejo carregar na pilha e deixando com que meu “toolkit” decida como tratar a instrução.
1 [Test] 2 public void TwoPlusTwo_2() 3 { 4 // arrange 5 var dm = IL.NewMethod(typeof(int)) 6 .LdcI4(2) 7 .Add(2) 8 .Ret(); 9 10 // act 11 var result = dm.Invoke(); 12 13 // assert 14 result.Should().Be(4); 15 }
O método LdcI4 carrega valores. O método Add também aceita parâmetros. Observe outra possibilidade:
1 [Test] 2 public void TwoPlusTwo() 3 { 4 // arrange 5 var dm = IL.NewMethod(typeof(int)) 6 .LdcI4(2, 2) 7 .Add() 8 .Ret(); 9 10 // act 11 var result = dm.Invoke(); 12 13 // assert 14 result.Should().Be(4); 15 }
Ou ainda…
1 [Test] 2 public void TwoPlusTwo_3() 3 { 4 // arrange 5 var dm = IL.NewMethod(typeof(int)) 6 .Add(2,2) 7 .Ret(); 8 9 // act 10 var result = dm.Invoke(); 11 12 // assert 13 result.Should().Be(4); 14 }
Tanto Add quanto LdcI4 aceitam número variável de argumentos. Bonito! ![]()
Um desafio maior … Fazendo emitting de um For
IL não possui facilitadores para loops (repetições). Na prática, o que temos são desvios condicionais simples e saltos no código (goto). Segue emitting para um método simples que soma todos os números de 1 até 100 (incluso).
1 [Test] 2 public void SumOf1To100_Reference() 3 { 4 var method = IL.NewMethod() 5 .Returns(typeof(int)) 6 .AsDynamicMethod; 7 8 var ilgen = method.GetILGenerator(); 9 ilgen.DeclareLocal(typeof(int)); // int var1; 10 ilgen.DeclareLocal(typeof(int)); // int var2 11 12 ilgen.Emit(OpCodes.Ldc_I4_1); 13 ilgen.Emit(OpCodes.Stloc_0); // var1 = 1; 14 15 var lbl = ilgen.DefineLabel(); // for ... 16 ilgen.MarkLabel(lbl); 17 18 ilgen.Emit(OpCodes.Ldloc_1); 19 ilgen.Emit(OpCodes.Ldloc_0); 20 ilgen.Emit(OpCodes.Add); 21 ilgen.Emit(OpCodes.Stloc_1); // var2 = var2 + var1 22 23 ilgen.Emit(OpCodes.Ldloc_0); 24 ilgen.Emit(OpCodes.Ldc_I4_1); 25 ilgen.Emit(OpCodes.Add); 26 ilgen.Emit(OpCodes.Stloc_0); // var1 = var1 + 1; 27 28 ilgen.Emit(OpCodes.Ldloc_0); 29 ilgen.Emit(OpCodes.Ldc_I4, 100); 30 ilgen.Emit(OpCodes.Ble, lbl); // if (var1 <= 100) .. 31 32 ilgen.Emit(OpCodes.Ldloc_1); 33 ilgen.Emit(OpCodes.Ret); // return var2 34 35 var result = method.Invoke(null, null); 36 37 int expected = 0; 38 for (int i = 0; i <= 100; i++) 39 expected += i; 40 41 result.Should().Be(expected); // expected = 5550; 42 }
Esse código apresenta alguns desafios interessantes:
- Há necessidade de usar variáveis para controlar o fluxo e o desvio para uma posição determinada no código.
- As variáveis são identificadas por uma sequência (no código, como há duas variáveis, há duas posições: 0 e 1).
- Os saltos precisam da definição de uma etiqueta de marcação de código (com marklabel).
Mais uma vez, essas são características comuns da IL que não precisariam ser preocupação de quem faz emitting. Logo, vamos para uma nova simplificação:
1 [Test] 2 public void SumOf1To100() 3 { 4 var method = IL.NewMethod() 5 .WithVariable(typeof(int), "count") 6 .WithVariable(typeof(int), "total") 7 .Returns(typeof(int)) 8 9 .LdcI4(1) 10 .Stloc("count") 11 12 .MarkLabel("start") 13 14 .Ldloc("total", "count") 15 .Add() 16 .Stloc("total") 17 18 .Ldloc("count") 19 .Add(1) 20 .Stloc("count") 21 22 .Ldloc("count") 23 .LdcI4(100) 24 .Ble("start") 25 26 .Ldloc("total") 27 .Ret(); 28 29 var result = method.Invoke(); 30 31 result.Should().Be(5050); 32 }
O que eu fiz?
- Substitui chamadas a índices por nomes (para variáveis);
- Continuo permitindo execução de mais de uma operação por chamada (como os dois Ldlocs na linha 14);
- Removo a lógica de criação de labels (deixo isso para o toolkit)
Mas, isso ainda poderia ser melhorado. Logo, …
1 [Test] 2 public void SumOf1To100_For() 3 { 4 var method = IL.NewMethod() 5 .WithVariable(typeof(int), "total") 6 .Returns(typeof(int)) 7 8 .For("count", 1, 100, 1) 9 10 .Ldloc("total", "count") 11 .Add() 12 .Stloc("total") 13 14 .Next() 15 16 .Ldloc("total") 17 .Ret(); 18 19 var result = method.Invoke(); 20 21 result.Should().Be(5050); 22 }
O que eu fiz? Implementei o For..Next.
Observe como o código fica mais simples. Ocultei todo tratamento condicional e saltos no toolkit. Sensivelmente melhor, não acha?
Implementando a melhoria
Os testes que escrevi mostram como a biblioteca de emitting poderia ser mais simples. Agora, demonstro como escrever todas as funcionalidades que apresentei.
Toda a implementação está em três classes principais. São elas:
- IL – um ponto de acesso para todo o toolkit;
- DynamicMethodInfo – Onde defino a DSL para definição da assinatura do método;
- DynamicMethodBody – Onde defino a DSL para definição do corpo do método.
Um ponto de acesso – A classe IL
1 public class IL 2 { 3 public static DynamicMethodBody NewMethod 4 (Type returnType, params Type[] parameterTypes) 5 { 6 DynamicMethodInfo result = new DynamicMethodInfo(); 7 8 foreach (var param in parameterTypes) 9 result.WithParameter(param); 10 11 result.Returns(returnType); 12 13 return result.Body; 14 } 15 16 public static DynamicMethodInfo NewMethod() 17 { 18 return new DynamicMethodInfo(); 19 } 20 }
Aqui, os dois métodos de acesso para definição do nosso método dinâmico.
- Na primeira sobrecarga recebo o tipo de retorno e dos parâmetros. Como toda a assinatura do método está definida, retorno diretamente uma instância de DynamicMethodBody. Assim, nossa “fluência” continua pela definição do corpo do método.
- Na segunda sobrecarga não recebo nada. Como toda a assinatura do método precisa ser definida, retorno DynamicMethod.
Variáveis e parâmetros
Tanto parâmetros quanto variáveis possuem uma representação comum com o tipo e com o nome da variável/parâmetro relacionado. Defini uma estrutura simples para essa representação. Observe:
1 public struct DynamicVariableInfo 2 { 3 public string Name { get; private set; } 4 public Type Type { get; private set; } 5 6 public DynamicVariableInfo(string name, Type type) 7 : this() 8 { 9 this.Name = name; 10 this.Type = type; 11 } 12 }
Você verá que utilizo essa utiliza para representar os parâmetros e variáveis definidas na fluência por WithParameter e WithVariable.
Facilitando a definição do método
Como já foi dito, a classe DynamicMethodInfo define a fluência para definição da assinatura do método. Seu construtor inicia automaticamente uma instância de DynamicMethodBody que definirá a fluência para o corpo do método.
1 public class DynamicMethodInfo 2 { 3 public DynamicMethodInfo() 4 { 5 this.Body = new DynamicMethodBody(this); 6 } 7 8 //... 9 }
Todos os parâmetros definidos na fluência por WithParameter ficam armazenadas na coleção _Parameters.
1 readonly List<DynamicVariableInfo> _Parameters = 2 new List<DynamicVariableInfo>(); 3 public IEnumerable<DynamicVariableInfo> Parameters 4 { 5 get 6 { 7 return _Parameters; 8 } 9 } 10 11 public DynamicMethodInfo WithParameter 12 (Type parameterType, string parameterName = "") 13 { 14 this._Parameters.Add( 15 new DynamicVariableInfo(parameterName, parameterType) 16 ); 17 return this; 18 }
Algo semelhante ocorre com as variáveis..
1 readonly List<DynamicVariableInfo> _Variables = 2 new List<DynamicVariableInfo>(); 3 public IEnumerable<DynamicVariableInfo> Variables 4 { 5 get 6 { 7 return _Variables; 8 } 9 } 10 11 public DynamicMethodInfo WithVariable 12 (Type variableType, string variableName = "") 13 { 14 this._Variables.Add( 15 new DynamicVariableInfo(variableName, variableType) 16 ); 17 return this; 18 }
O método Returns ficou definido como o responsável por concluir a definição da assinatura do método. Por isso, retorna a instância do assistente para definição do corpo do método.
1 public DynamicMethodBody Returns(Type type) 2 { 3 this.ReturnType = type; 4 return this.Body; 5 }
Por fim, defino algumas propriedades para representar o que já foi definido e que seja relevante externamente …
1 DynamicMethod _result; 2 public DynamicMethod AsDynamicMethod 3 { 4 get 5 { 6 if (_result == null) 7 { 8 var parameterTypes = _Parameters.Select(p => p.Type) 9 .ToArray(); 10 _result = new DynamicMethod("DynMethod", ReturnType, parameterTypes); 11 12 var ilgen = _result.GetILGenerator(); 13 foreach (var variable in this.Variables) 14 ilgen.DeclareLocal(variable.Type); 15 } 16 return _result; 17 } 18 } 19 20 public Type ReturnType { get; private set; } 21 22 public DynamicMethodBody Body { get; private set; }
E ainda mantenho uma sobrecarga de operador para permitir cast implícito para DynamicMethod.
1 public static implicit operator DynamicMethod(DynamicMethodInfo info) 2 { 3 return info.AsDynamicMethod; 4 }
Pronto!
Definindo o Corpo do método
Como já foi dito, DynamicMethodBody é a responsável por prover toda a fluência para a definição do corpo do método. É nela que ficam definidos nossos métodos “diretos” para IL. Começamos pelo construtor. Observe:
1 public class DynamicMethodBody 2 { 3 DynamicMethodInfo _Info; 4 internal DynamicMethodBody(DynamicMethodInfo info) 5 { 6 this._Info = info; 7 } 8 9 public DynamicMethod AsDynamicMethod 10 { 11 get 12 { 13 return _Info.AsDynamicMethod; 14 } 15 } 16 // .. 17 }
Mantenho uma referência para o objeto de definição do corpo do método. Para que isso? Para ter acesso as definições de variáveis e parâmetros.
O método Emit é um facilitador. É uma forma mais leve de chegar ao GetILGenerator. Além disso, é preparada para ser fluente.
1 #region Emit (basic) 2 public DynamicMethodBody Emit(OpCode opcode) 3 { 4 _Info.AsDynamicMethod.GetILGenerator() 5 .Emit(opcode); 6 7 return this; 8 } 9 10 public DynamicMethodBody Emit(OpCode opcode, int arg) 11 { 12 _Info.AsDynamicMethod.GetILGenerator() 13 .Emit(opcode, arg); 14 15 return this; 16 } 17 18 public DynamicMethodBody Emit(OpCode opcode, Label label) 19 { 20 _Info.AsDynamicMethod.GetILGenerator() 21 .Emit(opcode, label); 22 23 return this; 24 } 25 #endregion
Definimos alguns métodos para tornar nosso IL mais expressivo
1 #region Common Opcodes 2 public DynamicMethodBody Dup() 3 { 4 return this.Emit(OpCodes.Dup); 5 } 6 7 public DynamicMethodBody Ret() 8 { 9 return this.Emit(OpCodes.Ret); 10 } 11 #endregion
Nossos métodos para matemática base. Observe como suporto diversas operações em uma única chamada.
1 #region Basic Math Operations 2 private void MultipleOperations(Func<DynamicMethodBody> action, 3 params int[] args) 4 { 5 this.LdcI4(args); 6 if (args.Length == 1) 7 action(); 8 else 9 for (int i = 0; i < args.Length - 1; i++) 10 action(); 11 } 12 13 public DynamicMethodBody Add() 14 { 15 return this.Emit(OpCodes.Add); 16 } 17 18 public DynamicMethodBody Add(params int [] args) 19 { 20 this.MultipleOperations(this.Add, args); 21 return this; 22 } 23 24 public DynamicMethodBody Mul() 25 { 26 return this.Emit(OpCodes.Mul); 27 } 28 29 30 public DynamicMethodBody Mul(params int[] args) 31 { 32 this.MultipleOperations(this.Mul, args); 33 return this; 34 } 35 36 public DynamicMethodBody Div() 37 { 38 return this.Emit(OpCodes.Div); 39 } 40 41 public DynamicMethodBody Div(params int[] args) 42 { 43 this.MultipleOperations(this.Div, args); 44 return this; 45 } 46 47 public DynamicMethodBody Sub() 48 { 49 return this.Emit(OpCodes.Sub); 50 } 51 52 public DynamicMethodBody Sub(params int[] args) 53 { 54 this.MultipleOperations(this.Sub, args); 55 return this; 56 } 57 #endregion
Resolvo facilmente a operação de variáveis. Observe como utilizo a coleção de variáveis para traduzir “chave” por índice. Além disso, observe como otimizo a geração de instruções IL.
1 #region Locals (variables) 2 3 public int GetVariableIndex(string varname) 4 { 5 var variables = _Info.Variables.ToArray(); 6 7 for (int i = 0; i < variables.Length; i++) 8 if (variables[i].Name == varname) 9 return i; 10 11 return -1; 12 } 13 14 15 public DynamicMethodBody Ldloc(params uint [] args) 16 { 17 foreach (var arg in args) 18 { 19 switch (arg) 20 { 21 case 0: 22 Emit(OpCodes.Ldloc_0); 23 break; 24 case 1: 25 Emit(OpCodes.Ldloc_1); 26 break; 27 case 2: 28 Emit(OpCodes.Ldloc_2); 29 break; 30 case 3: 31 Emit(OpCodes.Ldloc_3); 32 break; 33 default: 34 Emit(OpCodes.Ldloc, (int)arg); 35 break; 36 37 } 38 } 39 return this; 40 } 41 42 public DynamicMethodBody Stloc(params uint[] args) 43 { 44 foreach (var arg in args) 45 { 46 switch (arg) 47 { 48 case 0: 49 Emit(OpCodes.Stloc_0); 50 break; 51 case 1: 52 Emit(OpCodes.Stloc_1); 53 break; 54 case 2: 55 Emit(OpCodes.Stloc_2); 56 break; 57 case 3: 58 Emit(OpCodes.Stloc_3); 59 break; 60 default: 61 Emit(OpCodes.Stloc, (int)arg); 62 break; 63 64 } 65 } 66 return this; 67 } 68 69 public DynamicMethodBody Ldloc(params string[] args) 70 { 71 foreach (var arg in args) 72 Ldloc((uint)GetVariableIndex(arg)); 73 74 return this; 75 } 76 77 public DynamicMethodBody Stloc(params string[] args) 78 { 79 var variables = _Info.Variables.ToArray(); 80 81 foreach (var arg in args) 82 Stloc((uint)GetVariableIndex(arg)); 83 84 return this; 85 } 86 #endregion
Outra vez, mesma lógica para argumentos/parâmetros. Observe:
1 #region Arguments (Parameters) 2 public DynamicMethodBody Ldarg(params uint [] args) 3 { 4 foreach (var arg in args) 5 { 6 switch (arg) 7 { 8 case 0: 9 Emit(OpCodes.Ldarg_0); 10 break; 11 case 1: 12 Emit(OpCodes.Ldarg_1); 13 break; 14 case 2: 15 Emit(OpCodes.Ldarg_2); 16 break; 17 case 3: 18 Emit(OpCodes.Ldarg_3); 19 break; 20 default: 21 Emit(OpCodes.Ldarg_S, (int)arg); 22 break; 23 24 } 25 } 26 return this; 27 } 28 29 public DynamicMethodBody Ldarg(params string[] args) 30 { 31 var parameters = _Info.Parameters.ToArray(); 32 33 foreach (var arg in args) 34 for (uint i = 0; i < parameters.Length; i++) 35 if (parameters[i].Name == arg) 36 Ldarg(i); 37 38 return this; 39 } 40 #endregion
Também otimizo a carga de constantes. Observe:
1 #region Constants 2 public DynamicMethodBody LdcI4(params int[] args) 3 { 4 foreach (var arg in args) 5 { 6 switch (arg) 7 { 8 case 0: 9 Emit(OpCodes.Ldc_I4_0); 10 break; 11 case 1: 12 Emit(OpCodes.Ldc_I4_1); 13 break; 14 case 2: 15 Emit(OpCodes.Ldc_I4_2); 16 break; 17 case 3: 18 Emit(OpCodes.Ldc_I4_3); 19 break; 20 case 4: 21 Emit(OpCodes.Ldc_I4_4); 22 break; 23 case 5: 24 Emit(OpCodes.Ldc_I4_5); 25 break; 26 case 6: 27 Emit(OpCodes.Ldc_I4_6); 28 break; 29 case 7: 30 Emit(OpCodes.Ldc_I4_7); 31 break; 32 case 8: 33 Emit(OpCodes.Ldc_I4_8); 34 break; 35 case -1: 36 Emit(OpCodes.Ldc_I4_M1); 37 break; 38 default: 39 Emit(OpCodes.Ldc_I4, arg); 40 break; 41 42 } 43 } 44 return this; 45 } 46 #endregion
O suporte para Labels fica fácil, também. Observe:
1 #region Labels 2 public DynamicMethodBody MarkLabel(Label label) 3 { 4 _Info.AsDynamicMethod.GetILGenerator() 5 .MarkLabel(label); 6 7 return this; 8 } 9 10 public DynamicMethodBody MarkLabel(string label) 11 { 12 _Info.AsDynamicMethod.GetILGenerator() 13 .MarkLabel(GetLabel(label)); 14 15 return this; 16 } 17 18 readonly Dictionary<string, Label> _Labels = 19 new Dictionary<string, Label>(); 20 Label GetLabel(string label) 21 { 22 if (!_Labels.ContainsKey(label)) 23 _Labels.Add(label, _Info.AsDynamicMethod.GetILGenerator() 24 .DefineLabel()); 25 26 return _Labels[label]; 27 } 28 #endregion
Comparação…
1 #region Comparasion 2 public DynamicMethodBody Ble(string label) 3 { 4 return Ble(GetLabel(label)); 5 } 6 7 DynamicMethodBody Ble(Label label) 8 { 9 return this.Emit(OpCodes.Ble, label); 10 } 11 12 public DynamicMethodBody Bge(string label) 13 { 14 return Bge(GetLabel(label)); 15 } 16 17 DynamicMethodBody Bge(Label label) 18 { 19 return this.Emit(OpCodes.Bge, label); 20 } 21 #endregion
E o astro dessa implementação. O facilitador do For
1 #region For..Next 2 readonly Stack<ForInfo> _Fors = new Stack<ForInfo>(); 3 public DynamicMethodBody For(string variable, int from, int to, int step = 1) 4 { 5 6 var ilgen = this.AsDynamicMethod.GetILGenerator(); 7 var lbl = ilgen.DefineLabel(); 8 9 _Fors.Push(new ForInfo(variable, from, to, step, lbl)); 10 if (GetVariableIndex(variable) == -1) 11 { 12 this._Info.WithVariable(typeof(int), variable); 13 ilgen.DeclareLocal(typeof(int)); 14 } 15 16 this 17 .LdcI4(from) 18 .Stloc(variable) 19 .MarkLabel(lbl); 20 21 return this; 22 } 23 24 public DynamicMethodBody Next() 25 { 26 var f = _Fors.Pop(); 27 this 28 .Ldloc(f.Variable) 29 .Add(f.Step) 30 .Stloc(f.Variable) 31 32 .Ldloc(f.Variable) 33 .LdcI4(f.To); 34 35 if (f.From < f.To) 36 this.Ble(f.GoTo); 37 else 38 this.Bge(f.GoTo); 39 40 return this; 41 } 42 #endregion
Bom.. cansei galera! Todo o código fonte está em: https://github.com/ElemarJR/FluentIL
Como vamos avançar? Escrevendo mais alguns testes e escrevendo melhorias na sintaxe.
Por hoje, era isso.
![]()






fevereiro 7th, 2011 → 1:06
[...] This post was mentioned on Twitter by Juan Lopes, Elemar Júnior. Elemar Júnior said: POST: FluentIL – Projeto Open-source para facilitar Emitting (parte 1) http://bit.ly/dX06iN #blog [...]
fevereiro 9th, 2011 → 21:17
[...] post anterior iniciamos a construção de um projeto open-source que objetiva facilitar escrita de código [...]