Home > OS >  Box and Unbox in ExpressionParameter or how to generically wrap every method
Box and Unbox in ExpressionParameter or how to generically wrap every method

Time:11-15

I have some domain which execute business logic. And I want to wrap every domain method with another method, which can extend the business logic. I do this with expressions. Everything works fine as long as the domain methods have parameters of reference types. As soon as they have a value typed parameter, my call to Expression.NewArrayInit crashes, because int cannot be used to initialize an array of type object. I know, that the solution is boxing and unboxing, but I don't know how.

This is my example code. I have two NUnit tests. The first one is for the domain method with reference type parameters, the second one is for the domain method with one value type parameter. I struggle with the second one. (By the way: I simplified the domain methods and the wrapping method.)

    [Test]
    public void Should_work_with_reference_types()
    {
        var domainBla = "This is the original Bla-Parameter";
        object domainBlubber = "This is the original Blubber-Parameter";

        var wrappedDomainMethod = BuildWrappedDomainMethod(new Action<string, object>(DomainMethod), "Before calling DomainMethod");

        wrappedDomainMethod.DynamicInvoke(domainBla, domainBlubber);
    }

    [Test]
    public void Should_work_with_value_types()
    {
        var domainBla = "This is the original Bla-Parameter";
        int domainBlubber = 73;

        var wrappedDomainMethod = BuildWrappedDomainMethod(new Action<string, int>(AnotherDomainMethod), "Before calling AnotherDomainMethod");

        wrappedDomainMethod.DynamicInvoke(domainBla, domainBlubber);
    }

    public Delegate BuildWrappedDomainMethod(Delegate domainMethod, string message)
    {
        var thisInstanceExpression = Expression.Constant(this);
        var wrappingMethodInfo = GetMethodInfo<ExpressionUnderstandingFixture>(f => f.WrappingMethod(null, null, null));
        var messageExpression = Expression.Constant(message);
        var domainMethodExpression = Expression.Constant(domainMethod);
        var parametersList = domainMethod.Method.GetParameters().Select(pi => Expression.Parameter(pi.ParameterType)).ToList();
        var parametersArrayNewExpression = Expression.NewArrayInit(typeof(object), parametersList);
        var wrappedMethodCallExpression = Expression.Call(
            thisInstanceExpression,
            wrappingMethodInfo,
            messageExpression,
            domainMethodExpression,
            parametersArrayNewExpression);
        var wrappedMethodCallLambdaExpression = Expression.Lambda(
            domainMethod.GetType(),
            wrappedMethodCallExpression,
            parametersList);
        var wrapedCallLambda = wrappedMethodCallLambdaExpression.Compile();
        return wrapedCallLambda;
    }

    public void WrappingMethod(string message, Delegate domainMethod, object[] parameters)
    {
        Console.WriteLine($"Message: {message}");
        domainMethod.DynamicInvoke(parameters);
    }

    public void DomainMethod(string bla, object blubber)
    {
        Console.WriteLine($"bla: {bla}");
        Console.WriteLine($"blubber: {blubber}");
    }

    public void AnotherDomainMethod(string bla, int blubber)
    {
        Console.WriteLine($"bla: {bla}");
        Console.WriteLine($"blubber: {blubber}");
    }

    public MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
    {
        var member = expression.Body as MethodCallExpression;

        if (member != null)
        {
            return member.Method;
        }

        throw new ArgumentException("Expression is not a method", "expression");
    }

CodePudding user response:

You can use Expression.Convert to box value type into object, and you can leave reference types as is:

var parametersArrayNewExpression = Expression.NewArrayInit(typeof(object), 
    parametersList.Select(x => x.Type.IsValueType ? (Expression) Expression.Convert(x, typeof(object)) : x));
  • Related