Elemar DEV

Tecnologia e desenvolvimento .net

FluentIL – Parte 14 – Lambda, Lambda, Lambda

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!

Por um código mais “generic”

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?!

Condicionais devem suportar Lambdas

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?!

While/Loop e Until/Loop

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.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

WordPress.com Logo

Você está comentando usando sua conta WordPress.com. Sair / Mudar )

Imagem do Twitter

Você está comentando usando sua conta Twitter. Sair / Mudar )

Foto do Facebook

Você está comentando usando sua conta Facebook. Sair / Mudar )

Conectando a %s

Informação

Publicado às 29/03/2012 por em Post e marcado , .

Estatísticas

  • 426,983 hits
%d bloggers like this: