Get a property name from a lambda expression *without* an object instance - c#

Asp.net MVC introduced the EditorFor method on the Html generic class. It allows you to write code that succinctly identifies a field of the view model. In this case the page this code is from must have a view model of some type that has a StudentId property or else this won't work.
#Html.EditorFor(m => m.StudentId)
The signature of the EditorFor property is something like this.
EditorFor<TModel,TValue>(HtmlHelper<TModel>, Expression<Func<TModel,TValue>>)
The method is defined on a generic type that knows the type of the TModel. So the lambda expression can be as simple as m => m.StudentId and we all know that the type of m is whatever TModel is. In this case it is the page's view model.
I would like to be able to write similar code, without knowing what type the property is defined on. I'd like to be able to write...
#Html.EditorFor(M.StudentId) // here M is a type not a lambda parameter
#Html.EditorFor(X.Y) // here X is a type not a lambda parameter
I'd like to be able to specify an arbitry type, and an arbitrary parameter, and have the method called with something that identifies both. For example if the method were called with a PropertyInfo then I could see both the property and the type it was defined on, both.
Said another way... In the same way that nameof(X.Y) gives a string "Y" for any arbitrary type X, I'd like an expression that gives something like a PropertyInfo too. Maybe property(X.Y) and you get back the PropertyInfo of the property Y from the type X.

You can use a property expression the same way as EditorFor. You can do this without an instance.
The one difference is you'll have to specify the type of the expression's parameter to tell the compiler what the object type is. So instead of
EditorFor( x => x.Name ); //Specifies a property but not a type
...your expression would look like this:
EditorFor( (MyType x) => x.Name ); //Specifies property and the type it belongs to
You can accomplish this with a short method like this one:
static PropertyInfo GetPropertyInfo<TIn, TOut>(Expression<Func<TIn, TOut>> expression)
{
var memberExp = expression.Body as MemberExpression;
return memberExp?.Member as PropertyInfo;
}
And then you can do this:
var p1 = GetPropertyInfo((string s) => s.Length);
Console.WriteLine("{0}.{1}", p1.DeclaringType.FullName, p1.Name);
var p2 = GetPropertyInfo((DateTime d) => d.Minute);
Console.WriteLine("{0}.{1}", p2.DeclaringType.FullName, p2.Name);
var p3 = GetPropertyInfo((Stream s) => s.CanSeek);
Console.WriteLine("{0}.{1}", p3.DeclaringType.FullName, p3.Name);
Output:
System.String.Length
System.DateTime.Minute
System.IO.Stream.CanSeek
Notice that we never needed any instances.

Related

Ignore a property with PropertyInfo

I want to ignore a property with a property info such that;
PropertyInfo propertyInfo = typeof(GLAccount).GetProperty("ExampleProp");
modelBuilder.Entity<GLAccount>().Ignore(g => propertyInfo);
The above code block gives me the following error;
The expression 'g => value(Dashboard.DAL.Context+<>c__DisplayClass16_0).propertyInfo' is not a valid property expression. The expression should represent a property: C#: 't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'.'
How can I solve this? Thanks.
If you're using an API that expects an exppression-tree, you will need to build the lambda manually:
var p = Expression.Parameter(typeof(GLAccount));
var body = Expression.Property(p, propertyInfo);
var lambda = Expression.Lambda<Func<GLAccount, SomeType>>(body, p);
modelBuilder.Entity<GLAccount>().Ignore(lambda);
The problem here, though, is going to be knowing the SomeType. I'm assuming that Ignore(...) is actually Ignore<TResult>(...) or similar, in which the TResult would need to be the same as the SomeType above, which would need to be whatever the return type of ExampleProp is. You may need to use MakeGenericMethod here.
Also note that if you aren't doing anything else with propertyInfo, you can also use Expression.Property(p, "ExampleProp") as a short-cut.
Although... from these docs, you might be able to simply use:
modelBuilder.Entity<GLAccount>().Ignore("ExampleProp");

Creating Linq Expression to convert int to enum name in AutoMapper projection

I'd like to streamline my current view model mapping. I currently use Automapper ProjectTo to create my view model, then call a post mapping method to populate a StatusDescription string property from a Status enum whose value is in another int property in the view model class, StatusId.
I found this other answer, but I am not able to get it to work, and am having a hard time understanding what exactly it is doing due to my general lack of experience with expressions.
For reference, here is the expression method (note I had to comment out the defaultValue parameter as I was getting an error An expression tree may not contain a call or invocation that uses optional arguments:
public static Expression<Func<TSource, String>> CreateEnumToStringExpression<TSource, TMember>(
Expression<Func<TSource, TMember>> memberAccess)//,string defaultValue = ""
{
string defaultValue = "";
var type = typeof(TMember);
if (!type.IsEnum)
{
throw new InvalidOperationException("TMember must be an Enum type");
}
var enumNames = Enum.GetNames(type);
var enumValues = (TMember[])Enum.GetValues(type);
var inner = (Expression)Expression.Constant(defaultValue);
var parameter = memberAccess.Parameters[0];
for (int i = 0; i < enumValues.Length; i++)
{
inner = Expression.Condition(
Expression.Equal(memberAccess.Body, Expression.Constant(enumValues[i])),
Expression.Constant(enumNames[i]),
inner);
}
var expression = Expression.Lambda<Func<TSource, string>>(inner, parameter);
return expression;
}
I am calling it like this:
.ForMember(dest => dest.Status, option =>
option.MapFrom(src =>
EnumHelper.ExpressionHelper
.CreateEnumToStringExpression((ShippingContainerHeader s) => s.StatusId)
))
The error I am getting when I run this is:
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression..
A.) I think I am calling this wrong maybe? Shouldn't I be using the reference to src in the MapFrom method call in order to pass the actual value of StatusId to the expression method?
B.) Seems like there is an issue with the expression generation method as it is producing the ToString() error. There is not an explicit .ToString() call in that method, but it must be implied somehow.
C.) Understanding the code: I can follow all of the code within the CreateEnumToStringExpression method except what the parameter variable is and the final expression variable expression statement. Also the generic types I'm confused on...there are a lot of generic types and the call to the method from the linked example doesn't explicitly define any of them.
In Expression<Func<TSource, String>> I'm pretty sure TSource is the input, which is an int. String is the output, which would be the enum name.
In CreateEnumToStringExpression<TSource, TMember> and Expression<Func<TSource, TMember>> memberAccess: TSource again is int, TMember is the enum class type.
But do you not have to explicitly call these out when calling the method? Something like: CreateEnumToStringExpression<int, StatusesEnum>(new Expression<Func<int, ShippingContainerHeader>> { s => s.StatusId })

How to convert PropertyInfo to property expression and use it to invoke generic method?

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

Accessing indexer from expression tree

I am working on a filtering function. The filter will be an expression tree build by an user. There will be about 30 fields the user can use for filtering. I think the best way is to create the object model with indexer and to access required values by index of enum type.
See this example:
enum Field
{
Name,
Date,
}
class ObjectModel
{
object this[Field Key]
{
get
{
//...
return xx;
}
}
}
I would like to ask how can I access an indexer from an expression tree.
I'll post a complete example on how to use an indexer:
ParameterExpression dictExpr = Expression.Parameter(typeof(Dictionary<string, int>));
ParameterExpression keyExpr = Expression.Parameter(typeof(string));
ParameterExpression valueExpr = Expression.Parameter(typeof(int));
// Simple and direct. Should normally be enough
// PropertyInfo indexer = dictExpr.Type.GetProperty("Item");
// Alternative, note that we could even look for the type of parameters, if there are indexer overloads.
PropertyInfo indexer = (from p in dictExpr.Type.GetDefaultMembers().OfType<PropertyInfo>()
// This check is probably useless. You can't overload on return value in C#.
where p.PropertyType == typeof(int)
let q = p.GetIndexParameters()
// Here we can search for the exact overload. Length is the number of "parameters" of the indexer, and then we can check for their type.
where q.Length == 1 && q[0].ParameterType == typeof(string)
select p).Single();
IndexExpression indexExpr = Expression.Property(dictExpr, indexer, keyExpr);
BinaryExpression assign = Expression.Assign(indexExpr, valueExpr);
var lambdaSetter = Expression.Lambda<Action<Dictionary<string, int>, string, int>>(assign, dictExpr, keyExpr, valueExpr);
var lambdaGetter = Expression.Lambda<Func<Dictionary<string, int>, string, int>>(indexExpr, dictExpr, keyExpr);
var setter = lambdaSetter.Compile();
var getter = lambdaGetter.Compile();
var dict = new Dictionary<string, int>();
setter(dict, "MyKey", 2);
var value = getter(dict, "MyKey");
To read from the indexer the IndexExpression contains directly the value of the indexed property. To write to it we must use Expression.Assign. Everything else is quite vanilla Expression. As written by Daniel the Indexer is normally called "Item". Note that Expression.Property has an overload that accepts directly the name of the indexer (so "Item"), but I chose to find it manually (so it can be reused). I have even put an example on how to use LINQ to find the exact overload of indexer you want.
Just as a curiosity, if you look on MSDN for example for Dictionary, under Properties you'll find Item
The indexer is a simple property, normally called Item. This means, you can access the indexer like any other property by using its name.
The name of the indexer property can be changed by the implementor of the class by means of the IndexerName attribute.
To reliably get the actual name of the indexer property, you have to reflect on the class and obtain the DefaultMember attribute.
More information can be found here.

How do I get a value of a reference type in an Expression?

I have this method:
public void DoSomething<T>(Expression<Func<T, object>> method)
{
}
If this method is called like this:
DoSomething(c => c.SomeMethod(new TestObject()));
... how do I get the value of the parameter that was passed into SomeMethod()?
If the parameter is a value type, this works:
var methodCall = (MethodCallExpression)method.Body;
var parameterValue = ((ConstantExpression)methodCall.Arguments[0]).Value;
However, when I pass in a reference type, methodCall.Arguments[0] is a MemberExpression, and I can't seem to figure out how to write code to get the value out of it.
Here is the answer (inspired by Akash's answer):
LambdaExpression lambda = Expression.Lambda(methodCall.Arguments[0]);
var compiledExpression = lambda.Compile();
return compiledExpression.DynamicInvoke();
You will have to evaluate member expression manually, MemberExpression contains "Expression" which is the container object of Member specified, to do this you can crete Lamda of your arguement and compile and execute it.
LamdaExpression l = Expression.Lambda(methodCall.Arguments[0]);
var c = l.Compile();
var v = c.Invoke();
So whatever you pass, you will get it in "v" variable.
This isn't really a matter of value type or reference type - it's a matter of a constant expression or not-constant expression. I'm sure your existing code would fail if you called
DoSomething(c => c.SomeMethod(DateTime.Now));
as well.
Basically, the argument to the method is just an expression. It's not a value. You could potentially compile that expression and then execute it to get the value at that point in time, but it's important to understand that an expression isn't a value in itself. In the case of a constant expression it's easy, but taking the DateTime.Now example, by definition the evaluated value of the expression changes over time :)
What are you trying to do with the argument? What's the bigger picture here?
Firstly, what Jon said.
There are no values to get ahold of, it's all just expressions. What might be of interest is the NewExpression, which has a Constructor property; this property contains the reflected constructor that would be called if you compiled expression and ran the resulting delegate. You could manually invoke that constructor and get an instance of what the user was intending on instantiating.

Categories