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));