i am trying to call an parameterized constructor from an expression instead of using the default ctor. this is the code that gets the constructor parameter(s):
ConstructorInfo ci = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, CallingConventions.HasThis, new[] { typeof(bool) }, new ParameterModifier[] { });
ParameterInfo[] paramsInfo = ci.GetParameters();
//create a single param of type object[]
ParameterExpression param = Expression.Parameter(typeof(bool), "il");
Expression[] argsExp = new Expression[paramsInfo.Length];
//pick each arg from the params array
//and create a typed expression of them
for (int i = 0; i < paramsInfo.Length; i++)
{
Expression index = Expression.Constant(i);
Type paramType = paramsInfo[i].ParameterType;
Expression paramAccessorExp = param;
//Expression.ArrayIndex(param, index);
Expression paramCastExp =
Expression.Convert(paramAccessorExp, paramType);
argsExp[i] = param;
}
NewExpression ci2 = Expression.New(ci, argsExp);
But if i try to compile the lambda expression i am getting the following error:
variable 'il' of type 'System.Boolean' referenced from scope '', but it is not defined"
What am i missing? Any help and/ or hint is appreciated.
You define a parameter called li in the 4th line of your code. In order to use this in a lambda expression, you need to have a scope in which this parameter is defined. You have two choices:
Create a BlockExpression that contains param as a local variable. Then use this expression as the body of your lambda expression.
Use param as a parameter in your LambdaExpression.
If you use option 1, you'll also have to initialize the variable. Otherwise you'll get a different kind of error message.
EDIT
There are two problems with the additional code you posted:
You need to use the same parameter object throughout your expression tree. Having the same name and type does not make two Parameter objects equal. I would simply move everything up to and including creating the lambda to the ConvertThis method so you can reuse the param variable. You can then just compile the return value of ConvertThis to get your delegate.
When creating the BlockExpression, you need to pass param in as a local variable. You do this by adding an argument, new ParameterExpression[] { param } to the method.
Related
I need to call the OrderBy<T, TKey>(Func<T, TKey>) method with a value for TKey only available at runtime. After reading answers on SO on how to use a variable as a generic parameter, I'm trying the following approach:
string key = "MyProperty";
Type keyType = typeof(T).GetProperty(key).PropertyType;
MethodInfo methodInfo = typeof(MyClass)
.GetMethod(
"MyGenericStaticMethod"),
BindingFlags.NonPublic | BindingFlags.Static);
// T is known at compile time.
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(new[] { typeof(T), keyType});
var expression = genericMethodInfo.Invoke(null, new object[] { params });
myQueryable.OrderBy(expression);
The problem is, genericMethodInfo.Invoke() returns object and hence cannot be used with OrderBy() which expects an argument of type Func<T, TKey>. However, TKey can be different value types like string,int that are only known at runtime. Can this even be done and if so, how?
Method MethodInfo.Invoke() is used to perform method call with provided parameters, and cannot be used to generate expression. To generate lambda Expression that can be used as an argument to .OrderBy() method, use this instead:
string key = "MyProperty";
Type keyType = typeof(T).GetProperty(key).PropertyType;
MethodInfo methodInfo = typeof(MyClass)
.GetMethod(
"MyGenericStaticMethod",
BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(new[] { typeof(T), keyType });
//this represents parameter of keySelector expression used in OrderBy method
var parameterExpression = Expression.Parameter(typeof(T));
// Expression representing call to MyGenericStaticMethod
var expression = Expression.Call(genericMethodInfo, parameterExpression);
// To use it as an argument of OrderBy method, we must convert expression to lambda
var lambda = Expression.Lambda(expression, parameterExpression);
You will probably encounter another problem: You cannot simply call myQueryable.OrderBy(lambda), because this doesn't allow compiller to infer it's generic arguments and you cannot provide these generic arguments, because TKey is not known at compile time. So you will need to do another reflection, to actually call an .OrderBy() method:
// OrderBy method has generic parameters and several overloads. It is thus
// difficult to get it's MethodInfo just by typeof(Queryable).GetMethod().
// Although it may seem weird, but it is easier to get it's MethodInfo from
// some arbitrary expression. Generic arguments "<object, object>" does not
// matter for now, we will replace them later
Expression<Func<IQueryable<object>, IQueryable<object>>> orderByExpression =
x => x.OrderBy<object, object>((o) => null);
// Replace generic parameters of OrderBy method with actual generic arguments
var orderByMethodInfo = (orderByExpression.Body as MethodCallExpression)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(new[] { typeof(T), keyType });
// Now we are finally ready to call OrderBy method
var orderedResultQuery = orderByMethodInfo.Invoke(
null,
new Object[] { myQueryable, lambda })
as IQueryable<T>;
// Just for testing purpose, let's materialize result to list
var orderedResult = orderedResultQuery.ToList();
I need to create an instance and set some properties. Normally I would use Expression.MemberInit(Expression.New(type), memberBindings), but I need to create the instance by calling a custom method instead. I can't figure out how to set members on the instance expression.
I'm creating the instance like this. I'm not actually using Activator.CreateInstance, but it should work for this example.
var method = typeof( Activator ).GetMethod( nameof( Activator.CreateInstance ), new Type[] { typeof( Type ) } );
Expression expression = Expression.Call( method, Expression.Constant( typeof( TestClass ) ) );
After I have the expression to create the instance, how can I set the objects members?
It's possible to create such expression with Expression.Block, but note that it can be used only to compile lambda delegate since the expression blocks are not supported by C# compiler and query providers other than LINQ to Objects.
Let first make sure the create instance method returns the correct type:
var method = typeof(Activator).GetMethod(nameof(Activator.CreateInstance), new Type[] { typeof(Type) });
var newObj = Expression.Convert(Expression.Call(method, Expression.Constant(typeof(TestClass))), typeof(TestClass));
and assuming you already have a list of MemberAssignment expressions which you'd normally use with Expression.MemberInit:
IEnumerable<MemberAssignment> memberBindings = ...;
Then the expression in question can be build as follows:
var obj = Expression.Variable(newObj.Type, "obj");
var expressions = new List<Expression>();
expressions.Add(Expression.Assign(obj, newObj));
expressions.AddRange(memberBindings.Select(b => Expression.Assign(
Expression.MakeMemberAccess(obj, b.Member), b.Expression)));
expressions.Add(obj);
var variables = new Expression[] { obj };
var result = Expression.Block(variables, expressions);
So we create a variable called obj, assign the result of the object creating method, then generate member assignments for each member binding, and finally return the obj (the last expression in the block expression list).
I am trying to build a system that loads function delegates to a dictionary and then they can be called from anywhere in the environment asking the dictionary for the delegate.
My functions are of a format Func<string, string>.
My code is
var methods = typeof(Keywords)
.GetMethods()
.Where(mt => mt.GetCustomAttributes(typeof(KDTAttribute), false).Count() > 0);
foreach (var method in methods)
{
string key = ((KDTAttribute)method.GetCustomAttributes(typeof(KDTAttribute), false)[0]).Keyword;
var combinedArgumentsExp = new Expression[] { Expression.Parameter(typeof(string),"param") };
var mtCall = Expression.Call(Expression.Constant(me), method,combinedArgumentsExp);
ParameterExpression targetExpr = Expression.Parameter(typeof(string), "param");
Func<string, string> result = Expression.Lambda<Func<string, string>>(mtCall, targetExpr).Compile();
retVal.Add(key, result);
}
I get the exception on the Expression.Lambda line :
variable 'param' of type 'System.String' referenced from scope '', but it is not defined.
P.S:
If there is a better way to load delegates to a dictionary at run time I'll be happy for any suggestions.
You're calling Expression.Parameter twice, which gives you different expressions. Don't do that - just call it once, and use that ParameterExpression both places you need it:
var parameter = Expression.Parameter(typeof(string),"param");
string key = ((KDTAttribute)method.GetCustomAttributes(typeof(KDTAttribute), false)[0]).Keyword;
var mtCall = Expression.Call(Expression.Constant(me), method, parameter);
var result = Expression.Lambda<Func<string, string>>(mtCall, parameter).Compile();
I'm trying to create a compiled expression delegate to call a constructor taking a single parameter, I'm receiving the following exception:
Additional information: variable 'value' of type 'MyType' referenced from scope '', but it is not defined
The code is as follows:
var constructorInfo = instanceType.GetConstructors().Skip(1).First();
ParameterExpression param = Expression.Parameter(genericArgument, "value");
Delegate constructorDelegate = Expression.Lambda(Expression.New(constructorInfo, new Expression[] { param })).Compile();
I believe I'm receving the exception because the parameter 'value' is not scoped inside an Expression.Block.
How do I scope the parameter & constructor expressions inside an Expression.Block?
In order to declare the parameter value, you need to also specify it when creating the Lambda expression (see this overload of the Expression.Lambda method). Up to now, you only create a parameterized lambda expression, but do not declare the parameters that are used in the expression. Changing your code should solve the issue:
var lambdaExpr = Expression.Lambda(Expression.New(constructorInfo,
new Expression[] { param }),
param);
Delegate constructorDelegate = lambdaExpr.Compile();
How to convert PropertyInfo to property expression which can be used to invoke StructuralTypeConfiguration<TStructuralType>.Ignore<TProperty>(Expression<Func<TStructuralType, TProperty>> propertyExpression) method?
I tried to use Expression.Property() to construct expression but I am getting following error when I use this expression as propertyExpression parameter:
The type arguments for method cannot be inferred from the usage. Try specifying the type arguments explicitly.
This error probably refers to TProperty type parameter which I don't know how to specify having only PropertyInfo.
I am doing this in relation to: Use Entity Framework's StructuralTypeConfiguration.Ignore() to Ignore all properties but specified set.
UPDATE
Code which is not working:
var propertyInfo = typeof(Foo).GetProperties()[0];
var expression = Expression.Default(typeof(Foo));
var expressionProperty = Expression.Property(expression, propertyInfo);
Ignore(expressionProperty);
var entityType = propertyInfo.DeclaringType;
var parameter = Expression.Parameter(entityType, "entity");
var property = Expression.Property(parameter, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(entityType, propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, parameter);
structureConfiguration.GetType()
.GetMethod("Ignore")
.MakeGenericMethod(propertyInfo.PropertyType)
.Invoke(structureConfiguration, new[]{lambda});
Property expressions require the property access to be on a specific object. There's a few options you can take here. First, if this is being done within one of your entity objects, you can simple use a ConstantExpression to build the property expression:
// Already have PropertyInfo in propInfo
Expression.Property(Expression.Constant(this, this.GetType()), propInfo)
However, since you need a Expression<Func<TStructuralType, TProperty>>, then it seems like you're going to have to build it using a ParameterExpression:
ParameterExpression pe = Parameter.Expression(typeof(MyEntity), "eParam");
Expression propExp = Expression.Property(pe, propInfo);
HOWEVER, here's the kicker... This is just a MemberExpression. To convert to the expression you need, you need to use Expression.Lambda to get a Func<> expression of the type you need. The problem? You don't know the type of the property to define the generic parameters of the lambda expression!
Expression<Func<MyEntity, ????>> eFunc = Expression.Lambda<Func<MyEntity, ????>>(propExp, pe);
This is the crux of the problem of doing it this way. That's not to say it can't be done... It's just that using this method IN THIS WAY isn't going to work. You'll have to use a bit runtime and static typing trickery (as well as judicious use of Actions instead of Funcs) to get this to work correctly.
TProperty exists only in the c# source code text. The compiler always resolves it to a concrete type. If you have a method
void Test<T>(T arg)
{
}
and call it like this
Test("hello");
Test(3);
The compiler generates code for two methods!
void Test(string arg)
{
}
void Test(int arg)
{
}
This means that you have to supply concrete types for your generic parameters if you want to have an invokable method.
This code will get you an Expression<Func<>> of the desired type. Note that there is an Expression.Lambda(...) override that doesn't need you to specify the type of the Func returned.
var t = typeof(Foo);
var pi = t.GetProperty(...);
var prm = Expression.Parameter(t, t.Name);
var prx = Expression.Property(prm, pi);
var lambda = Expression.Lambda(prx, prm);
Note that in many cases you don't have to bother with creating the Expression<Func<>> this way, assuming structureConfiguration below is a StructureConfiguration<Foo>, type inference will allow you to write something like this:
structureConfiguration.Ignore(f => f.Bar);