Olá pessoal. Tudo certo!?
Depois de publicar o site para FluentIL, comecei a receber bons feedbacks e sugestões sobre como aprimorar a biblioteca.
Hoje, implemento algumas sugestões do MVP Fábio Galuppo.
Essas “novidades” serão disponiblizadas no próximo release. Por hora, você pode consultar o repositório do projeto no GitHub.
Aceito sugestões!
Até aqui, quando FluentIL precisava requistar um tipo, fazia assim:
[Test]
public void TwoPlusTwoWithNamedParameters()
{
// arrange
var dm = IL.NewMethod()
.WithParameter(typeof(int), "a")
.WithParameter(typeof(int), "b")
.Returns(typeof(int))
.Ldarg("a", "b")
.Add()
.Ret();
// act
var result = dm.Invoke(2, 2);
// assert
result.Should().Be(4);
}
Galuppo recomendou algo assim:
[Test]
public void TwoPlusTwoWithNamedParameters()
{
// arrange
var dm = IL.NewMethod()
.WithParameter<int>("a")
.WithParameter<int>("b")
.Returns<int>()
.Ldarg("a", "b")
.Add()
.Ret();
// act
var result = dm.Invoke(2, 2);
// assert
result.Should().Be(4);
}
Bem mais limpo, não?!
Implementamos (Márcio Althmann fez essa modificação) mantendo a compatibilidade retroativa. Veja:
public DynamicMethodInfo WithParameter<T>(string parameterName = "")
{
return WithParameter(typeof (T), parameterName);
}
E fizemos isso sempre que aplicável. Ou seja, mantemos o método orignal e criamos uma sobrecarga. Bacana, não?!
FluentIL facilita o emitting de condicionais. Entretanto, poderia ser ainda mais simples. Veja a abordagem atual:
[Test]
public void MultipleConditions_4()
{
var dm = IL.NewMethod()
.WithParameter(typeof(int), "a")
.Returns(typeof(int))
.If("a>=10&&a<=20")
.Ldc(2)
.Else()
.Ldc(4)
.EndIf()
.Ret();
dm.Invoke(10).Should().Be(2);
dm.Invoke(9).Should().Be(4);
dm.Invoke(21).Should().Be(4);
}
O uso depende de alguma familiaridade com a interface. Eis como melhoramos:
[Test]
public void MultipleConditions_5()
{
var dm = IL.NewMethod()
.WithParameter(typeof(int), "a")
.Returns(typeof(int))
.If("a>=10&&a<=20",
@then: m => m.Ldc(2),
@else: m => m.Ldc(4)
)
.Ret();
dm.Invoke(10).Should().Be(2);
dm.Invoke(9).Should().Be(4);
dm.Invoke(21).Should().Be(4);
}
Fica mais simples, não?! Como fizemos?!
public DynamicMethodBody If(
string expression,
Action<DynamicMethodBody> @then
)
{
If(expression);
@then(this);
EndIf();
return this;
}
public DynamicMethodBody If(
string expression,
Action<DynamicMethodBody> @then,
Action<DynamicMethodBody> @else
)
{
If(expression);
@then(this);
Else();
@else(this);
EndIf();
return this;
}
Ou seja, “por baixo dos panos” continuamos usando a mesma interface. Apenas criamos “facilitadores para o código”. Fiz o mesmo com o For. Veja:
public DynamicMethodBody For(
string variable,
Number from,
Number to,
Action<DynamicMethodBody> @do
)
{
For(variable, from, to);
@do(this);
Next();
return this;
}
public DynamicMethodBody For(
string variable,
Number from,
Number to,
int step,
Action<DynamicMethodBody> @do
)
{
For(variable, from, to, step);
@do(this);
Next();
return this;
}
Com essas alterações, nosso IL para emitir o teste do primo, que era assim:
public IPrimeChecker CreatePrimeCheckerV4()
{
var t = IL.NewType().Implements<IPrimeChecker>()
.WithMethod("IsPrime")
.WithVariable(typeof(int), "i")
.WithParameter(typeof(int), "number")
.Returns(typeof(bool))
.If("number<=1")
.Ret(false)
.EndIf()
.For("i", 2, "number/2")
.If("(number%i)==0")
.Ret(false)
.EndIf()
.Next()
.Ret(true)
.AsType;
return (IPrimeChecker)Activator.CreateInstance(t);
}
Ficou assim:
public IPrimeChecker CreatePrimeCheckerV5()
{
var t = IL.NewType().Implements<IPrimeChecker>()
.WithMethod("IsPrime")
.WithVariable<int>("i")
.WithParameter<int>("number")
.Returns<bool>()
.If("number<=1", @then: m=>m
.Ret(false)
)
.For("i", 2, "number/2", @do: m => m
.If("(number%i)==0", @then: b => b
.Ret(false)
)
)
.Ret(true)
.AsType;
return (IPrimeChecker)Activator.CreateInstance(t);
}
Menos “verboso”. Não é mesmo?!
Até aqui, FluentIL tinha suporte para repetições com For. Agora, também oferece suporte para loops While e Until. Veja um exemplo com While:
public IPrimeChecker CreatePrimeCheckerV6()
{
var t = IL.NewType().Implements<IPrimeChecker>()
.WithMethod("IsPrime")
.WithVariable<int>("i")
.WithParameter<int>("number")
.Returns<bool>()
.If("number<=1", @then: m => m
.Ret(false)
)
.Stloc(2, "i")
.While("i <= number/2", @do: m => m
.If("(number%i)==0", @then: b => b
.Ret(false)
)
.Inc("i")
)
.Ret(true)
.AsType;
return (IPrimeChecker)Activator.CreateInstance(t);
}
Agora, uma implementação com Until:
public IPrimeChecker CreatePrimeCheckerV7()
{
var t = IL.NewType().Implements<IPrimeChecker>()
.WithMethod("IsPrime")
.WithVariable<int>("i")
.WithParameter<int>("number")
.Returns<bool>()
.If("number<=1", @then: m => m
.Ret(false)
)
.Stloc(2, "i")
.Until("i > number/2", @do: m => m
.If("(number%i)==0", @then: b => b
.Ret(false)
)
.Inc("i")
)
.Ret(true)
.AsType;
return (IPrimeChecker)Activator.CreateInstance(t);
}
Bacana, não?! Veja como fiz a implementação disso na DSL:
using System;
using System.Collections.Generic;
using FluentIL.Infos;
// ReSharper disable CheckNamespace
namespace FluentIL.Emitters
// ReSharper restore CheckNamespace
{
partial class DynamicMethodBody
{
private readonly Stack<WhileInfo> whilesField = new Stack<WhileInfo>();
private DynamicMethodBody WhileOrUntil(string condition, bool isWhile)
{
var ilgen = methodInfoField.GetILEmitter();
var beginLabel = ilgen.DefineLabel();
var comparasionLabel = ilgen.DefineLabel();
whilesField.Push(new WhileInfo(
isWhile ? condition : "!(" + condition + ")",
beginLabel,
comparasionLabel));
return Br(comparasionLabel)
.MarkLabel(beginLabel);
}
public DynamicMethodBody While(string condition)
{
return WhileOrUntil(condition, true);
}
public DynamicMethodBody Until(string condition)
{
return WhileOrUntil(condition, false);
}
public DynamicMethodBody While(
string condition,
Action<DynamicMethodBody> @do
)
{
While(condition);
@do(this);
Loop();
return this;
}
public DynamicMethodBody Until(
string condition,
Action<DynamicMethodBody> @do
)
{
Until(condition);
@do(this);
Loop();
return this;
}
public DynamicMethodBody Loop()
{
var w = whilesField.Pop();
return MarkLabel(w.ComparasionLabel)
.If(w.Condition, m => m
.Br(w.BeginLabel)
);
}
}
}
Era isso.