Supporting "out / ref" parameters in expressions with conversion to "object" - c#

My journey to this question started with an implementation of Jon Skeet's article: "Making reflection fly and exploring delegates":
and in it, he states:
Note: I was going to demonstrate this by calling DateTime.AddDays, but for value type instance methods the implicit first first parameter is passed by reference, so we’d need a delegate type with a signature of DateTime Foo(ref DateTime original, double days) to call CreateDelegate. It’s feasible, but a bit of a faff. In particular, you can’t use Func<...> as that doesn’t have any by-reference parameters.
I am using my own delegates in an effort to support this and I am just totally stumped trying to create compiled expressions in C# .NET 4.0 to support "out / ref" but converting the delegate types to "object".
When I run the code below, I get the expected output in the first case, however, in the second case (when I convert the input and output parameters to type: object) the "out" parameter is not assigned and the result is "before" as opposed to "after".
public class Test
{
public delegate void myDelegate<T, U>(T test, out U setMe);
public void myFunction(out string setMe)
{
setMe = "after";
}
}
private static playin.program.Test.myDelegate<Test, string> buildExactExpression(MethodInfo methodInfo)
{
ParameterExpression instance = Expression.Parameter(typeof(Test));
ParameterExpression argument = Expression.Parameter(typeof(string).MakeByRefType());
var methodCall = Expression.Call(
instance,
methodInfo,
argument);
return Expression.Lambda<playin.program.Test.myDelegate<Test, string>>(methodCall, new ParameterExpression[] { instance, argument }).Compile();
}
private static playin.program.Test.myDelegate<object, object> buildDesiredExpression(MethodInfo methodInfo)
{
ParameterExpression instance = Expression.Parameter(typeof(object));
ParameterExpression argument = Expression.Parameter(typeof(object).MakeByRefType());
var methodCall = Expression.Call(
Expression.Convert(instance, typeof(Test)),
methodInfo,
Expression.Convert(argument, typeof(string)));
return Expression.Lambda<playin.program.Test.myDelegate<object, object>>(methodCall, new ParameterExpression[] { instance, argument }).Compile();
}
static void Main(string[] args)
{
Test t1 = new Test();
var myFunctionMethodInfo = t1.GetType().GetMethod("myFunction");
//this one works, the "out" string is set to "after"
string someString = "before";
var compiledExactly = buildExactExpression(myFunctionMethodInfo);
compiledExactly(t1, out someString);
Console.WriteLine(someString);
//the following doesn't return the expected output, the "out" parameter is not set, "before" is printed
object someObjectString = "before";
var compiledObject = buildDesiredExpression(myFunctionMethodInfo);
compiledObject(t1, out someObjectString);
Console.WriteLine(someObjectString);
}
For some objects in my prog, I am discovering their methods at runtime and do not know the parameter types ahead of time, so the conversion to "object" (buildDesiredExpression method) in the delegate that is returned is v.important. I would like to cache the returned "open" delegates so that I can run them with a minimal performance penalty during execution.
How can I fix the "buildDesiredExpression" method to make this work?

What you can do is to create a local variable of the right type, let the called method set that, and then set the parameter:
ParameterExpression instance = Expression.Parameter(typeof(object));
ParameterExpression argument =
Expression.Parameter(typeof(object).MakeByRefType());
ParameterExpression argumentVariable = Expression.Parameter(typeof(string));
var methodCall = Expression.Call(
Expression.Convert(instance, typeof(Test)),
methodInfo,
argumentVariable);
var block = Expression.Block(
new[] { argumentVariable },
methodCall, Expression.Assign(argument, argumentVariable));
return Expression.Lambda<Test.myDelegate<object, object>>(
block, new[] { instance, argument }).Compile();

Related

Is there a way to instantiate a class via Expression.New where 'ConstructorInfo' is supplied by an ExpressionParameter?

I would like to instantiate a (non-generic) class in C# (ClassToBeInstantiated) that has a public non-parameterless constructor via LINQ Expression.New inside an Expression.Block.
see update below, based on answer from #sweeper
Description
According to the documentation, the only overloads for Expression.New that do accept arguments require a ConstructorInfo argument. As I do not have access to that info beforehand, but have to retrieve this inside the Expression.Block.
So I am able to use an Expression.Call on the type ClassToBeInstantiated that is passed into the block.
However, all Expression.New overloads only accept either ConstructorInfo as an argument or for an instantiation if I want to pass arguments to the constructor. Type is only available for using a parameterless constructor.
I cannot use a ParameterExpression holding a ConstructorInfo either.
Question
So my question: is there a way around this with Expression.New? I know I can use ConstructorInfo.Invoke via another Expression.Call. But this seems akward to me, as -at least in my opinion- the Expression.New API should exactly do this for me.
Am I missing something?
Thanks for your help, comments and reply.
The class to be instantiated
Here is some additional information to further illustrate the case:
public class ClassToBeInstantiated
{
public ClassToBeInstantiated(int count) { }
}
Helper class to retrieve ConstructorInfo
public static class GetConstructor
{
public static ConstructorInfo GetNonGenericConstructorInfo(Type type, params Type[] typeArguments) =>
type.GetConstructor(typeArguments);
}
[Test]
public void InstantiateTypeViaExpressionNew()
{
var typeExpression = Expression.Parameter(typeof(Type), "type");
var countExpression = Expression.Parameter(typeof(int), "count");
var ctorExpression = Expression.Variable(typeof(ConstructorInfo), "constructorInfo");
var block = Expression.Block
(
new[] { ctorExpression },
Expression.Assign(ctorExpression, Expression.Call(typeof(GetConstructor), nameof(GetConstructor.GetNonGenericConstructorInfo),
Type.EmptyTypes, typeExpression, Expression.Constant(new[] { countExpression.Type }))),
Expression.New(/* error - obviously does not compile: ctorInfo */, countExpression)
);
var lambda = Expression.Lambda<Func<Type, int, object>>(block, typeExpression, countExpression);
var func = lambda.Compile();
var o = func.Invoke(typeof(ClassToBeInstantiated), 4);
var instance = o as ClassToBeInstantiated;
Assert.NotNull(instance);
}
Update with Expression.Call instead of Expression.New
I updated the code example from my original question, based on the answer from #Sweeper to provide a full example (in case someone is interested):
Updated helper class
Here, I added the field constructorInfoInvokeMethodInfo for retrieval of the MethodInfo for the ConstructorInfo.Invoke() method (to be called from inside the Expression.Block:
public static class GetConstructor
{
public static ConstructorInfo GetNonGenericConstructorInfo(Type type, params Type[] typeArguments) =>
type.GetConstructor(typeArguments);
public static readonly MethodInfo constructorInfoInvokeMethodInfo =
typeof(ConstructorInfo).GetMethod(nameof(ConstructorInfo.Invoke), new[] { typeof(object[]) });
}
Updated test for Expression.Block
Here, I replaced the (non-working) Expression.New with an Expression.Call to instantiate the type via ConstructorInfo.Invoke():
[Test]
public void InstantiateTypeViaExpressionCall()
{
var typeExpression = Expression.Parameter(typeof(Type), "type");
var countExpression = Expression.Parameter(typeof(int), "count");
var ctorExpression = Expression.Variable(typeof(ConstructorInfo), "constructorInfo");
var block = Expression.Block
(
new[] { ctorExpression },
Expression.Assign
(
ctorExpression,
Expression.Call(typeof(Activator), nameof(GetNonGenericConstructorInfo), Type.EmptyTypes, typeExpression, Expression.Constant(new[] { countExpression.Type }))
),
Expression.Call(ctorExpression, constructorInfoInvokeMethodInfo, Expression.NewArrayInit(typeof(object), Expression.Convert(countExpression, typeof(object))))
);
var lambda = Expression.Lambda<Func<Type, int, object>>(block, typeExpression, countExpression);
var func = lambda.Compile();
var o = func.Invoke(typeof(ClassToBeInstantiated), 4);
var instance = o as ClassToBeInstantiated;
Assert.NotNull(instance);
}
So my question: is there a way around this with Expression.New? I know I can use ConstructorInfo.Invoke via another Expression.Call.
This is exactly what you should do.
Think about the expression tree that you want to create. Since the final usage that you want to achieve looks like:
func.Invoke(typeof(ClassToBeInstantiated), 4);
func's expression must look like:
(type, count) => GetConstructor.GetNonGenericConstructorInfo(type, typeof(int)).Invoke(new object[] {count})
This seems to be what you were trying to do too, just with an extra local variable.
It cannot be:
(type, count) => new type(count)
Because new type(count) is not a valid expression. type is just a parameter.
This has nothing to do with new expressions that Expression.New creates. There are no NewExpressions in your desired expression at all. It would be very weird if Expression.New suddenly starts inserting ConstructorInfo.Invoke calls in the expression tree, because that is not what it is supposed to create at all.
You could use Expression.New here to create expressions such as:
(type, count) => new ClassToBeInstantiated(count)
But I don't think that's what you want... You want the type parameter to determine what type to instantiate, and assume that a one-parameter int constructor to be available, right?
Using sharplab.io, you can write the lambda expression you want, and see what code the compiler actually generates for building the expression tree. After cleaning up the code that generates for
Expression<Func<Type, int, object>> func =
(type, count) => GetConstructor.GetNonGenericConstructorInfo(type, typeof(int)).Invoke(new object[] {count});
You get:
MethodInfo getConstructorMethod = typeof(GetConstructor).GetMethod(nameof(GetConstructor.GetNonGenericConstructorInfo));
MethodInfo invokeMethod = typeof(ConstructorInfo).GetMethod(nameof(ConstructorInfo.Invoke), new Type[] { typeof(object[]) });
ParameterExpression typeParam = Expression.Parameter(typeof(Type), "type");
ParameterExpression countParam = Expression.Parameter(typeof(int), "count");
// GetNonGenericConstructorInfo(type, typeof(int))
MethodCallExpression instance = Expression.Call(null, getConstructorMethod,
typeParam, Expression.NewArrayInit(typeof(Type), Expression.Constant(typeof(int), typeof(Type)))
);
// // .Invoke(new object[] { count })
MethodCallExpression body = Expression.Call(instance, invokeMethod,
Expression.NewArrayInit(typeof(object), Expression.Convert(countParam, typeof(object)))
);
Expression<Func<Type, int, object>> func = Expression.Lambda<Func<Type, int, object>>(body, typeParam, countParam);
And func.Compile()(typeof(ClassToBeInstantiated), 4) as ClassToBeInstantiated is not null.

Can I dynamically create an expression representing a lambda which calls a method on the input parameter?

Let's say I have an object of a certain class A.
Let's say I need an Expression which calls method M on class A.
Is this even doable?
Basically, I need to programatically get this lambda
a => a.M();
The trick is, I want to do this generically, i.e. I plan to use reflection to figure out that the method is called M and what parameters it wants.
I tried using Expression.Lambda(MethodCallExpression method, ParameterExpression params).
The issue there is that when I define the method call expression, I have to specify an instance (or leave it at null if it's a static method, which it isn't). I don't want this. I want whatever the parameter is passed into the lambda (a in my example) to be the instance.
Is this possible?
Yes, it's possible to construct a linq expression at a runtime.
E.g. below is an example of constructing an expression of a method call which returns an object. This is really a dummy example as it's better to avoid object in favor of strict types.
static Expression<Func<T, object>> ComposeMethodCallExpressionAsFuncObject<T>(string methodName)
{
MethodInfo mi = typeof(T).GetMethod(methodName, types: new Type[0])
?? throw new ArgumentException($"There is no '{methodName}' method in the '{typeof(T).Name}' with the empty arguments list!");
var paramExpression = Expression.Parameter(typeof(T));
var methodCallExpression = Expression.Call(paramExpression, mi);
var result = Expression.Lambda<Func<T, object>>(methodCallExpression, paramExpression);
return result; // (T obj) =>obj.methodName()
}
, and example of usage:
int foo = 9988;
var expression = ComposeMethodCallExpressionAsFuncObject<int>(nameof(int.ToString));
//expression: (int obj) => obj.ToString()
var result = expression.Compile()(foo);
Assert.AreEqual("9988", result);

How to handle Closure and Static Method Call in Expression Tree?

The Following Code tries to Create a Func delegate that converts from the original func Argument Type to another type.
public static Delegate Convert<T1, R>(this Func<T1, R> func, Type argType)
{
var param = Expression.Parameter(argType);
var convertedParam = new Expression[] { Expression.Convert(param, typeof(T1))};
var call = Expression.Convert(
func.Target == null || func.Target is Closure
? Expression.Call(func.Method, Expression.Constant(func.Target), convertedParam[0])// this path causes the error
: Expression.Call(Expression.Constant(func.Target), func.Method, convertedParam), typeof(R));
var delegateType = typeof(Func<,>).MakeGenericType(argType, typeof(R));
var lambda = Expression.Lambda(delegateType, call, param);
return lambda.Compile();// BUG: 'MethodInfo must be a runtime MethodInfo object.
}
My problem starts when the Func contains a closure as Target, lambda.Compile() bugs saying "Method Info must be a runtime MethodInfo Object" i suspect it's because the method is static.
Can someone please explain to me what i am doing wrong? and why? i obviously don't understand Expressions well enough to fix this on my own.
Thanks in advance.
You should call Expression.Invoke, which will call a delegate directly.
Pass it Expression.Constant(func).

Explicit conversion of parameters in Expression Trees created from MethodInfo

I have the method below which converts a (non-static) MethodInfo into a compiled Expression (Func) which I can then call.
This works great: I can call it with a method expecting both reference objects and value types.
BUT unlike the original method where I could call a method that had a parameter expecting a double and pass it an int this compiled expression doesn't support that and throws an InvalidCastException.
How do I modify this to support the same type of implicit casting that happens during a normal method call?
Bonus question: should the instanceExp use the DeclaringType or the ReflectedType from the MethodInfo?
public Func<object, object[], object> Create(MethodInfo methodInfo)
{
var methodParams = methodInfo.GetParameters();
var arrayParameter = Expression.Parameter(typeof(object[]), "array");
var arguments =
methodParams.Select((p, i) => Expression.Convert(
Expression.ArrayAccess(arrayParameter, Expression.Constant(i)), p.ParameterType))
.Cast<Expression>()
.ToList();
var instanceParameter = Expression.Parameter(typeof(object), "controller");
var instanceExp = Expression.Convert(instanceParameter, methodInfo.DeclaringType);
var callExpression = Expression.Call(instanceExp, methodInfo, arguments);
var bodyExpression = Expression.Convert(callExpression, typeof(object));
return Expression.Lambda<Func<object, object[], object>>(
bodyExpression, instanceParameter, arrayParameter)
.Compile();
}
--- EDIT
The working solution is:
var changeTypeMethod = typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(TypeCode) });
var arguments =
methodParams.Select((p, i) =>
!typeof(IConvertible).IsAssignableFrom(p.ParameterType)
// If NOT IConvertible, don't try to convert it
? (Expression)Expression.Convert(
Expression.ArrayAccess(arrayParameter, Expression.Constant(i)), p.ParameterType)
:
// Otherwise add an explicit conversion to the correct type to handle int <--> double etc.
(Expression)Expression.Convert(
Expression.Call(changeTypeMethod,
Expression.ArrayAccess(arrayParameter, Expression.Constant(i)),
Expression.Constant(Type.GetTypeCode(p.ParameterType))),
p.ParameterType)
)
.ToList();
The problem is the same as in this piece of C# code:
object a = 123;
double b = (double)a; // InvalidCastException
The reason is that a is an object, so in order to make it a double the cast must unwrap it, and then transform an int to double. The language allows the cast to do only one thing - it will either unwrap or transform, but not both. You need to tell the compiler how to do this cast explicitly by telling it that there is an int wrapped inside the object:
double b = (double)((int)a); // Works
If you could do the same thing in your LINQ expression, your compiled expression will work as well. However, you may not know the actual type of the parameter at the time you generate your expression, so you may want to go for a different strategy - wire in a call to Convert.ChangeType method, which can unwrap and cast at the same time.

Is it possible to have an out ParameterExpression?

I want to define a Lambda Expression with an out parameter. Is it possible to do it?
Below are code snippets from a C# .Net 4.0 console app that I tried.
As you can see in Procedure25 I can use lambda expressions to define a delegate that has an output parameter, however, when I want to use linq expressions to do the same, the code in procedure 24 fails with:
System.ArgumentException was unhandled Message=ParameterExpression
of type 'System.Boolean' cannot be used for delegate parameter of type
'System.Boolean&' Source=System.Core
I know I could use an input class object with a bool member and pass back the value to the caller that way but I was curious if I could somehow define out parameters.
Thanks
static void Main(string[] args)
{
Procedure25();
Procedure24();
Console.WriteLine("Done!");
Console.ReadKey();
}
private delegate int Evaluate(string value, out bool usesVars);
private static void Procedure24()
{
// This fails to compile:
//Expression<Evaluate> x = (string val, out bool usesSimVals) =>
//{
// usesSimVals = true;
// Console.WriteLine(val);
// return 1;
//};
ParameterExpression valueParameter = Expression.Parameter(typeof (string));
MethodCallExpression methodCall = Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(object) }), valueParameter);
bool usesVars;
ParameterExpression usesVarsParameter = Expression.Parameter(typeof (bool), "out usesVars");
Expression.Lambda<Evaluate>(methodCall, valueParameter, usesVarsParameter).Compile()("test", out usesVars);
Console.WriteLine(usesVars);
}
private static void Procedure25()
{
Evaluate x = (string value, out bool vars) => { vars = true;
Console.WriteLine(value);
return 1;
};
bool usesVars;
x("test", out usesVars);
}
EDIT:
Ani, awesome, thanks. So the key thing was to call MakeByRefType on the parameter type.
For the record here is a code snippet that works based on Ani's suggestion:
private static void Procedure24()
{
ParameterExpression valueParameter = Expression.Parameter(typeof (string));
MethodCallExpression methodCall = Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(object) }), valueParameter);
bool usesVars;
ParameterExpression usesVarsParameter = Expression.Parameter(typeof (bool).MakeByRefType(), "out usesVars");
Expression block = Expression.Block(methodCall, Expression.Assign(usesVarsParameter, Expression.Constant(true)), Expression.Constant(1));
int result = Expression.Lambda<Evaluate>(block, valueParameter, usesVarsParameter).Compile()("test", out usesVars);
Console.WriteLine("Result={0}, usesVars={1}", result, usesVars);
}
You need Type.MakeByRefType:
var usesVarsParameter = Expression.Parameter(typeof(bool).MakeByRefType(), "usesVars");
Note that your code sample has an additional problem: your expression-body isn't correct - it's not returning a value when it should be returning an int to satisfy the delegate-type's return-type.
Here's a way you can fix that (like your lambda example):
var body = Expression.Block(methodCall, Expression.Constant(1));
Expression.Lambda<Evaluate>(body, valueParameter, usesVarsParameter)
.Compile()("test", out usesVars);
Also note that you are not assigning the out parameter inside the expression. Expression.Lambda is letting you get away with it, which I didn't expect, but hey, the BCL doesn't have to follow the same rules as C#!

Categories

Resources