Proxies dinâmicos usando Emitting (avançado) – Parte 3/3 [final]

Publicado em 15/05/2011

1


Olá pessoal, como estamos?

Este é o terceiro e último post de uma (mini) série mostrando como criar proxies dinâmicos com emitting.

No primeiro post, mostrei como criar objetos dinamicamente que repassam chamadas de métodos para implementações concretas (proxies).

image_thumb2

No segundo post, mostrei como introduzir um “monitor” que seria “avisado” toda vez que um método fosse evocando, antes (BeforeExecute) e depois (AfterExecute).

image_thumb6

Hoje, finalizo a série com um pouco de refactoring e a criação de monitores com suporte a Expressions..

Todo código está disponível em https://github.com/elemarjr/FluentIL

Etapa …17 – Refactoring

Encerrei o post anterior mencionando que o código tinha um Bad smell sério, meus métodos estavam ficando grandes demais. Minhas diretrizes foram:

  1. tentar manter métodos com menos de 10 linhas;
  2. remover condicionais explícitos (retirar os if do código);
  3. utilizar fluência para deixar o código mais legível.

O resultado final ficou bacana. Observe:

public static class ProxyBuilder
{
    public static T CreateProxy<T>(
        T concreteInstance, 
        IProxyMonitor monitor = null
        )
    {
        return CreateInstance<T>(
            IL.NewType().Implements<T>()
                .EmitConcreteInstanceSupport<T>()
                .EmitProxyMonitorSupport(condition: monitor != null)
                .EmitMethods<T>(monitor),
            concreteInstance, monitor);
    }

    private static T CreateInstance<T>(
        this DynamicTypeInfo that,
        T concreteInstance,
        IProxyMonitor monitor = null
        )
    {
        var type = that.AsType;
        var result = (T)Activator.CreateInstance(type);

        var setupConcreteInstance = type.GetMethod("__SetConcreteInstance");
        setupConcreteInstance.Invoke(result, new object[] { concreteInstance });

        if (monitor != null)
        {
            var setupProxyMonitor = type.GetMethod("__SetProxyMonitor");
            setupProxyMonitor.Invoke(result, new object[] { monitor });
        }
        return result;
    }

    private static DynamicTypeInfo EmitMethods<T>(
        this DynamicTypeInfo that,
        IProxyMonitor monitor
        )
    {
        foreach (var method in typeof(T).GetMethods())
            EmitMethod(that, method, monitor);
        return that;
    }

    private static void EmitMethod(
        DynamicTypeInfo t, 
        MethodInfo method,
        IProxyMonitor monitor = null
        )
    {
        var body = EmitMethodSignature(t, method, monitor)
            .EmitBeforeExecuteCall(condition: monitor != null, 
					method: method)
            .EmitConcreteMethodCall(method)
            .EmitAfterExecuteCall(condition: monitor != null, 
					method: method)
            .Ret();
    }

    private static DynamicMethodBody EmitConcreteMethodCall(
        this DynamicMethodBody body,
        MethodInfo method )
    {
        var parameters = method.GetParameters();

        return body
            .Ldarg(0)
            .Ldfld("__concreteinstance")
            .Repeater(0, parameters.Length - 1, 1, (index, b) => b
                .Ldarg(parameters[index].Name)
                )
            .Call(method);
    }

    private static DynamicMethodBody EmitMethodSignature(
		DynamicTypeInfo t, 
		MethodInfo method, 
		IProxyMonitor monitor)
    {
        var ilmethod = t.WithMethod(method.Name);
        var parameters = method.GetParameters();

        foreach (var param in parameters)
            ilmethod.WithParameter(
                param.ParameterType,
                param.Name
                );

        if (monitor != null)
        {
            if (method.ReturnType != typeof(void))
                ilmethod.WithVariable(method.ReturnType);

            if (parameters.Length > 0)
                ilmethod.WithVariable(typeof(object[]), "parameters");
        }

        return ilmethod
            .Returns(method.ReturnType);
    }

    private static DynamicMethodBody EmitBeforeExecuteCall(
        this DynamicMethodBody body,
        bool condition,
        MethodInfo method
        )
    {
        if (!condition) return body;

        var beforeExecuteMi = typeof(IProxyMonitor)
            .GetMethod("BeforeExecute");

        var parameters = method.GetParameters();

        return body
            .Ldarg(0).Dup()
            .Ldfld("__proxymonitor")
            .Ldstr(method.Name)
            .Newarr(typeof(object), parameters.Length)
            .EmitIf( parameters.Length > 0, b => b
                .Stloc("parameters")
                .Repeater(0, parameters.Length - 1, 1, (index, b1) => b1
                    .Ldloc("parameters")
                    .Ldc(index)
                    .Ldarg((uint)(index + 1))
                    .Box(parameters[index].ParameterType)
                    .Emit(OpCodes.Stelem_Ref)
                    )
                .Ldloc("parameters")
                )
            .Call(beforeExecuteMi)
            //
            .Ldfld("__proxymonitor")
            .Ldstr(method.Name);
    }

    private static DynamicMethodBody EmitAfterExecuteCall(
        this DynamicMethodBody body,
        bool condition,
        MethodInfo method )
    {
        if (!condition) return body;
        var afterExecuteMi = typeof(IProxyMonitor)
            .GetMethod("AfterExecute");

        if (method.ReturnType != typeof(void))
            body
                .Stloc(0)
                .Ldloc(0)
                .Box(method.ReturnType);
        else
            body.Ldnull();

        body
            .Call(afterExecuteMi);

        if (method.ReturnType != typeof(void))
            body.Ldloc(0);

        return body;
    }

    private static DynamicTypeInfo EmitConcreteInstanceSupport<T>(
        this DynamicTypeInfo that)
    {
        that
            .WithField("__concreteinstance", typeof(T))
            .WithMethod("__SetConcreteInstance")
            .WithParameter(typeof(T))
            .Returns(typeof(void))
                .Ldarg(0)
                .Ldarg(1)
                .Stfld("__concreteinstance")
                .Ret();
        return that;
    }

    private static DynamicTypeInfo EmitProxyMonitorSupport(
        this DynamicTypeInfo that, 
        bool condition
        )
    {
        if (!condition) return that;

        that
            .WithField("__proxymonitor", typeof(IProxyMonitor))
            .WithMethod("__SetProxyMonitor")
            .WithParameter(typeof(IProxyMonitor))
            .Returns(typeof(void))
                .Ldarg(0)
                .Ldarg(1)
                .Stfld("__proxymonitor")
                .Ret();

        return that;
    }
}

Etapa 18 – Criar um monitor genérico com suporte a Expressions

Ter que implementar uma classe inteira toda vez que desejamos “monitorar” o consumo de uma classe parece ser um desperdício. Por causa disso, criei uma implementação genérica. Observe:

public class ExpressionProxyMonitor
    : IProxyMonitor
{
    public Action<string, object []> BeforeExecuteAction { get; set; }
    public void BeforeExecute(string methodName, object[] p)
    {
        if (this.BeforeExecuteAction == null)
            return;

        this.BeforeExecuteAction(methodName, p);
    }

    public Action<string, object> AfterExecuteAction { get; set; }
    public void AfterExecute(string methodName, object result)
    {
        if (this.AfterExecuteAction == null)
            return;

        this.AfterExecuteAction(methodName, result);
    }
}

Agora, posso utilizar essa classe para criar “monitores” mais rapidamente.

Etapa 19 – Que tal um helper?!

Resolvi adicionar um helper para tornar a criação do proxy mais expressiva. Eis o método que incluí em ProxyBuilder.

public static T CreateProxy<T>(
    T concreteInstance, 
    Action<string, object []> beforeExecuteAction,
    Action<string, object> afterExecuteAction
    )
{
    var monitor = new ExpressionProxyMonitor()
    {
        BeforeExecuteAction = beforeExecuteAction,
        AfterExecuteAction = afterExecuteAction
    };

    return CreateProxy<T>(concreteInstance, monitor);
}

Pronto! Estamos mais expressivos.

Etapa 20 – Testando nosso “monitor genérico”

Vamos ao nosso teste. Afinal, temos que garantir que nosso monitor funciona Smiley de boca aberta… Ou, pelo menos, não possui erros evidentes.

[Test]
public void CreateProxy_PassingExpressions()
{
    // arrange
    var foo = new Foo();
    int cc = 0;
    var target = ProxyBuilder.CreateProxy<IFoo>(
        foo,
        beforeExecuteAction: (s, o) =>
            cc += 2,
        afterExecuteAction: (s, o) =>
            cc--
        );
    // act
    target.Add(2, 3);
    // assert
    cc.Should().Be(1);
}

Green bar!!

Enfim, o fim! Smiley de boca aberta

Por hoje, era isso.

Publicado em: Emitting, Post