I am trying to create a method for (at runtime) creating wrappers for delegates of all types. This to create a flexible way of injecting additional logging (in this case). In this first step i tried to create a try-catch wrap around the given input
-argument.
try
{
Console.WriteLine(....);
// Here the original call
Console.WriteLine(....);
}
catch(Exception ex)
{
Console.WriteLine(.....);
}
I am using a generic method call CreateWrapper2
(see below)
private static readonly MethodInfo ConsoleWriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string), typeof(object[]) });
private static MethodCallExpression WriteLinExpression(string format, params object[] args)
{
Expression[] expressionArguments = new Expression[2];
expressionArguments[0] = Expression.Constant(format, typeof(string));
expressionArguments[1] = Expression.Constant(args, typeof(object[]));
return Expression.Call(ConsoleWriteLine, expressionArguments);
}
public T CreateWrapper2<T>(T input)
{
Type type = typeof(T);
if (!typeof(Delegate).IsAssignableFrom(type))
{
return input;
}
PropertyInfo methodProperty = type.GetProperty("Method");
MethodInfo inputMethod = methodProperty != null ? (MethodInfo)methodProperty.GetValue(input) : null;
if (inputMethod == null)
{
return input;
}
string methodName = inputMethod.Name;
ParameterInfo[] parameters = inputMethod.GetParameters();
ParameterExpression[] parameterExpressions = new ParameterExpression[parameters.Length];
// TODO: Validate/test parameters, by-ref /out with attributes etc.
for (int idx = 0; idx < parameters.Length; idx++)
{
ParameterInfo parameter = parameters[idx];
parameterExpressions[idx] = Expression.Parameter(parameter.ParameterType, parameter.Name);
}
bool handleReturnValue = inputMethod.ReturnType != typeof(void);
ParameterExpression variableExpression = handleReturnValue ? Expression.Variable(inputMethod.ReturnType) : null;
MethodCallExpression start = WriteLinExpression("Starting '{0}'.", methodName);
MethodCallExpression completed = WriteLinExpression("Completed '{0}'.", methodName);
MethodCallExpression failed = WriteLinExpression("Failed '{0}'.", methodName);
Expression innerCall = Expression.Call(inputMethod, parameterExpressions);
LabelTarget returnTarget = Expression.Label(inputMethod.ReturnType);
LabelExpression returnLabel = Expression.Label(returnTarget, Expression.Default(returnTarget.Type)); ;
GotoExpression returnExpression = null;
if (inputMethod.ReturnType != typeof(void))
{
// Handle return value.
innerCall = Expression.Assign(variableExpression, innerCall);
returnExpression = Expression.Return(returnTarget, variableExpression, returnTarget.Type);
}
else
{
returnExpression = Expression.Return(returnTarget);
}
List<Expression> tryBodyElements = new List<Expression>();
tryBodyElements.Add(start);
tryBodyElements.Add(innerCall);
tryBodyElements.Add(completed);
if (returnExpression != null)
{
tryBodyElements.Add(returnExpression);
}
BlockExpression tryBody = Expression.Block(tryBodyElements);
BlockExpression catchBody = Expression.Block(tryBody.Type, new Expression[] { failed, Expression.Rethrow(tryBody.Type) });
CatchBlock catchBlock = Expression.Catch(typeof(Exception), catchBody);
TryExpression tryBlock = Expression.TryCatch(tryBody, catchBlock);
List<Expression> methodBodyElements = new List<Expression>();
if(variableExpression != null) methodBodyElements.Add(variableExpression);
methodBodyElements.Add(tryBlock);
methodBodyElements.Add(returnLabel);
Expression<T> wrapperLambda = Expression<T>.Lambda<T>(Expression.Block(methodBodyElements), parameterExpressions);
Console.WriteLine("lambda:");
Console.WriteLine(wrapperLambda.GetDebugView());
return wrapperLambda.Compile();
}
For void-methods (like Action<>
) this code does what i need. But when there is a return value i get the exception " variable '' of type 'System.Boolean' referenced from scope '', but it is not defined "
Many other posts talk about Expression.Parameter
called more than once for a parameter; to me it look like here is something else is wrong here but i can't find it. All goes well untill the .Compile
line, there it crashes.
For a Func<int, bool> target = i => i % 2 ==0;
below is the DebugView for the generated expression.
.Lambda #Lambda1<System.Func`2[System.Int32,System.Boolean]>(System.Int32 $i) {
.Block() {
$var1;
.Try {
.Block() {
.Call System.Console.WriteLine(
"Starting '{0}'.",
.Constant<System.Object[]>(System.Object[]));
$var1 = .Call LDAP.LdapProgram.<Main>b__0($i);
.Call System.Console.WriteLine(
"Completed '{0}'.",
.Constant<System.Object[]>(System.Object[]));
.Return #Label1 { $var1 }
}
} .Catch (System.Exception) {
.Block() {
.Call System.Console.WriteLine(
"Failed '{0}'.",
.Constant<System.Object[]>(System.Object[]));
.Rethrow
}
};
.Label
.Default(System.Boolean)
.LabelTarget #Label1:
}
}
What am i missing? (during the debugging hours i tried:
Expression.Variable
from inside the try-body to the toplevel. Expression.Return
. )
It looks like you aren't specifying your varaibles for the block statement.
In the error you're creating a parameter on the fly and not giving it a name, if you did you would have seen :
"variable 'varName' of type 'System.Boolean' referenced from scope 'varName', but it is not defined"
So for future reference can make your life a lot easier if you give your vars names when making expression trees the following should work
// Define the variable at the top of the block
// when we are returning something
if (variableExpression != null)
{
block = Expression.Block(new[] { variableExpression }, methodBodyElements);
}
else
{
block = Expression.Block(methodBodyElements);
}
Expression<T> wrapperLambda = Expression<T>.Lambda<T>(block, parameterExpressions);
return wrapperLambda.Compile();
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.