Get Full Class Property "Tree" Name as String - c#

What I am trying to do is probably a little strange.
But I am trying to (this is the best I can explain it) use reflection to get a full class property tree name as a string.
Successful example so far:
By using expressions I am able to convert:
() => Model.Cargo.Id
into the string:
"Model.Cargo.Id"
My problem now is when I am using an array in the mix, I do not get the array name. All I get is the last properties name.
Unsuccessful example:
Model.CargoTasks[j].IsSet
Only returns me the string:
"IsSet"
Ideally I want the following string result:
"Model.CargoTasks[0].IsSet"
I am probably asking a little much to get the index included in the result, but it would be fantasic if this were possible.
The code I am using to process these examples is as follows:
public static string ToMemberAccess<TResult>(this Expression<Func<TResult>> expression)
{
// Get the body of the expression
Expression body = expression.Body;
if (body.NodeType != ExpressionType.MemberAccess && body.NodeType != ExpressionType.Convert)
{
throw new ArgumentException("Property expression must be of the form '() => SomeProperty'", "expression");
}
var memberExpression = expression.Body as MemberExpression ?? ((UnaryExpression)expression.Body).Operand as MemberExpression;
var stuff = GetMemberNames(memberExpression);
stuff.Reverse();
return string.Join(".", stuff);
}
static List<string> GetMemberNames(MemberExpression expression, List<string> actual = null)
{
if (actual == null) actual = new List<string>();
var member = expression.Member;
var subExp = expression.Expression as MemberExpression;
actual.Add(member.Name);
if(subExp != null) actual = GetMemberNames(subExp, actual);
return actual;
}
Thanks in advance! Any help will be greatly appreciated!

To get the value in the indexer, you must compile and execute the expression - which is prohibitively expensive, but it can be done using a modified version of ExpressionStringBuilder. Note that I've added a parameter, compileConstants. When it's set to false, the output will be something like Model.CargoTasks[_.j].IsSet.
Note that this sample visitor is incomplete (i.e. it doesn't support all kinds of expressions). You can complement it using the code in GitHub.
public static string ToMemberAccess<TResult>(Expression<Func<TResult>> expression, bool compileConstants = false)
{
var builder = new ExpressionStringBuilder(compileConstants);
builder.Visit(expression);
return builder.ToString();
}
internal class ExpressionStringBuilder : ExpressionVisitor
{
private readonly bool _compileConstants;
private readonly StringBuilder _out;
public ExpressionStringBuilder(bool compileConstants)
{
_compileConstants = compileConstants;
_out = new StringBuilder();
}
protected override Expression VisitConstant(ConstantExpression node)
{
if (node.Value != null)
{
string text = node.Value.ToString();
if (node.Value is string)
{
Out("\"");
Out(text);
Out("\"");
}
else if (text == node.Value.GetType().ToString())
{
Out('_');
}
else
{
Out(text);
}
}
else
{
Out("null");
}
return node;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
int num = 0;
Expression expression = node.Object;
if (Attribute.GetCustomAttribute(node.Method, typeof(ExtensionAttribute)) != null)
{
num = 1;
expression = node.Arguments[0];
}
var name = node.Method.Name;
var isIndexer = name == "get_Item";
if (expression != null)
{
Visit(expression);
if (!isIndexer)
{
Out('.');
}
}
if (isIndexer)
Out('[');
else
{
Out(name);
Out('(');
}
int i = num;
int count = node.Arguments.Count;
while (i < count)
{
if (i > num)
{
Out(", ");
}
VisitArgument(node.Arguments[i]);
i++;
}
Out(isIndexer ? ']' : ')');
return node;
}
protected override Expression VisitIndex(IndexExpression node)
{
if (node.Object != null)
{
Visit(node.Object);
}
else
{
Out(node.Indexer.DeclaringType.Name);
}
if (node.Indexer != null)
{
Out(".");
Out(node.Indexer.Name);
}
Out('[');
for (var index = 0; index < node.Arguments.Count; index++)
{
if (index > 0)
{
Out(", ");
}
var expression = node.Arguments[index];
VisitArgument(expression);
}
Out(']');
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
Visit(node.Body);
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
OutMember(node.Expression, node.Member);
return node;
}
public override string ToString()
{
return _out.ToString();
}
private void VisitArgument(Expression expression)
{
if (_compileConstants)
{
// TODO: possibly check the expression is not dependent on parameters
var value = Expression.Lambda(expression).Compile().DynamicInvoke();
Out(value + string.Empty);
}
else
{
VisitArgument(expression);
}
}
private void OutMember(Expression instance, MemberInfo member)
{
if (instance != null)
{
Visit(instance);
if (_out.Length > 0)
Out('.');
Out(member.Name);
return;
}
Out(member.DeclaringType.Name + "." + member.Name);
}
private void Out(char c)
{
_out.Append(c);
}
private void Out(string s)
{
_out.Append(s);
}
}

Related

lambda expression function body to sql query

I would like to know if there is any way to do the next. I would like to extract compiled body of a function.
class Program
{
public static void Main(string[] args)
{
int i = 5;
Where2(p => new { V = p.Length == i });
}
public static void Where2<TResult>(Expression<Func<string, TResult>> func)
{
try
{
var body = (System.Linq.Expressions.NewExpression)func.Body;
Console.WriteLine("Output:");
Console.WriteLine(body);
Console.ReadLine();
}
catch (Exception e)
{
throw e;
}
//dynamic operation = func.Body; //
}
}
Output: new <>f__AnonymousType0`1(V = (p.Length ==
value(TestDelegateFun.Program+<>c__DisplayClass0_0).i))
So I would like to have the compiled body like :
Output: new <>f__AnonymousType0`1(V = (p.Length ==
5))
public class FilterConstructor : ExpressionVisitor
{
private static readonly Dictionary<ExpressionType, string> _logicalOperators;
private static readonly Dictionary<Type, Func<object, string>> _typeConverters;
static FilterConstructor()
{
//mappings for table, shown above
_logicalOperators = new Dictionary<ExpressionType, string>
{
[ExpressionType.Not] = "not",
[ExpressionType.GreaterThan] = "gt",
[ExpressionType.GreaterThanOrEqual] = "ge",
[ExpressionType.LessThan] = "lt",
[ExpressionType.LessThanOrEqual] = "le",
[ExpressionType.Equal] = "==",
[ExpressionType.Not] = "not",
[ExpressionType.AndAlso] = "and",
[ExpressionType.OrElse] = "or"
};
//if type is string we will wrap it into single quotes
//if it is a DateTime we will format it like datetime'2008-07-10T00:00:00Z'
//bool.ToString() returns "True" or "False" with first capital letter, so .ToLower() is applied
//if it is one of the rest "simple" types we will just call .ToString() method on it
_typeConverters = new Dictionary<Type, Func<object, string>>
{
[typeof(string)] = x => $"'{x}'",
[typeof(DateTime)] =
x => $"'{((DateTime)x).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")}'",
[typeof(bool)] = x => x.ToString().ToLower()
};
}
private StringBuilder _queryStringBuilder;
private Stack<string> _fieldNames;
public FilterConstructor()
{
//here we will collect our query
_queryStringBuilder = new StringBuilder();
//will be discussed below
_fieldNames = new Stack<string>();
}
//entry point
public string GetQuery(LambdaExpression predicate)
{
Visit(predicate.Body);
var query = _queryStringBuilder.ToString();
_queryStringBuilder.Clear();
return query;
}
//protected override Expression VisitUnary(UnaryExpression node)
//{
// //assume we only allow not (!) unary operator:
// if (node.NodeType != ExpressionType.Not)
// throw new NotSupportedException("Only not(\"!\") unary operator is supported!");
// _queryStringBuilder.Append($"{_logicalOperators[node.NodeType]} ");//!
// _queryStringBuilder.Append("("); //(!
// //go down from a tree
// Visit(node.Operand); //(!expression
// _queryStringBuilder.Append(")"); //(!expression)
// //we should return expression, it will allow to create new expression based on existing one,
// //but, at our case, it is not needed, so just return initial node argument
// return node;
//}
//corresponds to: and, or, greater than, less than, etc.
protected override Expression VisitBinary(BinaryExpression node)
{
_queryStringBuilder.Append("("); //(
//left side of binary operator
Visit(node.Left); //(leftExpr
_queryStringBuilder.Append($" {_logicalOperators[node.NodeType]} ");//(leftExpr and
//right side of binary operator
Visit(node.Right); //(leftExpr and RighExpr
_queryStringBuilder.Append(")"); //(leftExpr and RighExpr)
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression.NodeType == ExpressionType.Constant
||
node.Expression.NodeType == ExpressionType.MemberAccess)
{
_fieldNames.Push(node.Member.Name);
Visit(node.Expression);
}
else
//corresponds to: x.Customer - just write "Customer"
_queryStringBuilder.Append(node.Member.Name);
return node;
}
//corresponds to: 1, "Tom", instance of NameSpace+<>c__DisplayClass12_0, instance of Order, i.e.
//any expression with value
protected override Expression VisitConstant(ConstantExpression node)
{
//just write value
_queryStringBuilder.Append(GetValue(node.Value));
return node;
}
private string GetValue(object input)
{
var type = input.GetType();
//if it is not simple value
if (type.IsClass && type != typeof(string))
{
//proper order of selected names provided by means of Stack structure
var fieldName = _fieldNames.Pop();
var fieldInfo = type.GetField(fieldName);
object value;
if (fieldInfo != null)
//get instance of order
value = fieldInfo.GetValue(input);
else
//get value of "Customer" property on order
value = type.GetProperty(fieldName).GetValue(input);
return GetValue(value);
}
else
{
//our predefined _typeConverters
if (_typeConverters.ContainsKey(type))
return _typeConverters[type](input);
else
//rest types
return input.ToString();
}
}
}

Retrieving nested PropertyInfo via expressions

I'm trying to create a function where I can pass in an expression to say which properties I'm interested in. I have it working for top level properties but not for nested properties.
Example model
public class Foo {
public string Name { get; set; }
public List<Foo> List { get; set; }
}
What I have so far
private PropertyInfo GetPropertyInfo<TModel>(Expression<Func<TModel, object>> selector)
{
if (selector.NodeType != ExpressionType.Lambda)
{
throw new ArgumentException("Selector must be lambda expression", nameof(selector));
}
var lambda = (LambdaExpression)selector;
var memberExpression = ExtractMemberExpression(lambda.Body);
if (memberExpression == null)
throw new ArgumentException("Selector must be member access expression", nameof(selector));
if (memberExpression.Member.DeclaringType == null)
{
throw new InvalidOperationException("Property does not have declaring type");
}
return memberExpression.Member.DeclaringType.GetProperty(memberExpression.Member.Name);
}
private static MemberExpression ExtractMemberExpression(Expression expression)
{
if (expression.NodeType == ExpressionType.MemberAccess)
{
return ((MemberExpression)expression);
}
if (expression.NodeType == ExpressionType.Convert)
{
var operand = ((UnaryExpression)expression).Operand;
return ExtractMemberExpression(operand);
}
return null;
}
So:
GetPropertyInfo<Foo>(x => x.Name); // works
GetPropertyInfo<Foo>(x => x.List.Select(y => y.Name); <-- how do I get this?
I'm looking for a way to pick any property from a complex object.
You need to extend your ExtractMemberExpression just a bit to accept Select call expression:
private MemberExpression ExtractMemberExpression(Expression expression) {
if (expression.NodeType == ExpressionType.MemberAccess) {
return ((MemberExpression) expression);
}
if (expression.NodeType == ExpressionType.Convert) {
var operand = ((UnaryExpression) expression).Operand;
return ExtractMemberExpression(operand);
}
if (expression.NodeType == ExpressionType.Lambda) {
return ExtractMemberExpression(((LambdaExpression) expression).Body);
}
if (expression.NodeType == ExpressionType.Call) {
var call = (MethodCallExpression) expression;
// any method named Select with 2 parameters will do
if (call.Method.Name == "Select" && call.Arguments.Count == 2) {
return ExtractMemberExpression(call.Arguments[1]);
}
}
return null;
}

Passing property list as strongly typed parameters

I have this method which extracts the property name from an expresssion:
private static string GetPropertyName<TObj, TProp>(Expression<Func<TObj, TProp>> prop)
{
var expression = prop.Body as MemberExpression;
if (expression != null)
{
var property = expression.Member as PropertyInfo;
if (property != null)
{
return property.Name;
}
}
return string.Empty;
}
So later I can use it like this:
GetPropertyName((User u) => u.Surname); //Returns "Surname"
I would like to be able to pass a collection of properties instead one by one. Just to be clear, the properties could be of different types.
I am completely agree with #Patrick and its preferred way over mine.
but if you say you are not using the C#6.0 and then you can use the code you have written. I just use the param, yield return and one foreach loop
private static IEnumerable<string> GetPropertyName<TObj, TProp>(params Expression<Func<TObj, TProp>>[] propCollection)
{
foreach (var prop in propCollection)
{
var expression = prop.Body as MemberExpression;
if (expression != null)
{
var property = expression.Member as PropertyInfo;
if (property != null)
{
yield return property.Name;
}
}
yield return string.Empty;
}
}
UPDATE
First One ask you to specific the type of the object again and again mean you have to provide the full length expression again.
Try the below it will ask you to specify the property as much as you want in one Expression only.
public static IEnumerable<string> GetPropertiesName<TObj, TProp>(Expression<Func<TObj, TProp[]>> prop)
{
var array = (prop.Body as NewArrayExpression);
var exp = array == null ? null : array.Expressions;
if (exp != null)
{
//var expArr = (prop.Body as NewArrayExpression).Expressions;
foreach (var oneProp in exp)
{
Expression onePropExp;
if (oneProp.GetType() == typeof (UnaryExpression))
{
onePropExp = (oneProp as UnaryExpression).Operand;
}
else
{
onePropExp = oneProp;
}
var property = (onePropExp as MemberExpression).Member as PropertyInfo;
if (property != null)
{
yield return property.Name;
}
yield return string.Empty;
}
}
yield return string.Empty;
}
You can call it like -
var propNames = GetPropertiesName((AllSubsTransAndDevices d) => new[]
{
d.CurrentDriverId,
d.GPSDevicesId,
d.TransporterId
});
It might be me, but I think you don't need to do this the hard way. You can simply use the C# 6 nameof keyword. This assumes you can use C# 6 of course.
string name = nameof(u.Surname);
Try this:
Usage: string[] props = GetPropertiesName((MainWindow m) => m.Lalala, (MainWindow m) => m.Lalala);
private static string[] GetPropertiesName<TObj, TProp>(params Expression<Func<TObj, TProp>>[] prop)
{
List<string> ret = new List<string>();
foreach (var item in prop)
ret.Add(GetPropertyName(item));
return ret.ToArray();
}
private static string GetPropertyName<TObj, TProp>(Expression<Func<TObj, TProp>> prop)
{
var expression = prop.Body as MemberExpression;
if (expression != null)
{
var property = expression.Member as PropertyInfo;
if (property != null)
{
return property.Name;
}
}
return string.Empty;
}

Expression Tree GetString Result

I am trying to copy the behavior of Entity Framework in creating the query from expression and i found my way using ExpressionVisitor when getting the property of the model by using Attribute
this is what i got so far
internal class NVisitor : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
private readonly Type _type;
public NVisitor(Type type)
{
_type = type;
_parameter = Expression.Parameter(type);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameter;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.MemberType == MemberTypes.Property)
{
var memberName = node.Member.Name;
PropertyInfo otherMember = _type.GetProperty(memberName);
var ncols = node.Member.GetCustomAttributes(typeof(NColumn), true);
if (ncols.Any())
{
var ncol = (NColumn)ncols.First();
otherMember = _type.GetProperty(ncol.Name);
}
var inner = Visit(node.Expression);
return Expression.Property(inner, otherMember);
}
return base.VisitMember(node);
}
}
i have an attribute NColumn that indicates the real name of the property from table column so i mark the property of the model by Attribute
public class BonusTypeX
{
[NColumn("BonusTypeName")]
public string Name { get; set; }
}
now when im trying to get the expression,
[TestMethod]
public void ExpressionTesting2()
{
string searchKey = "Xmas";
Expression<Func<BonusTypeX, bool>> expression = x => x.Name.Contains(searchKey);
Type t = typeof(tbl_BonusType);
var body = new NVisitor(t).Visit(expression.Body);
string a = string.Join(".", body.ToString().Split('.').Skip(1));
Assert.AreEqual("BonusTypeName.Contains(\"Xmas\")", a);
}
i got this
BonusTypeName.Contains(value(Payroll.Test.Administration.TestRepositories+<>c__DisplayClass13).searchKey)
what i am expecting to get is
BonusTypeName.Contains("Xmas")
is there any method that gets the expression string? i am using
string a = string.Join(".", body.ToString().Split('.').Skip(1));
which i think it might be wrong.. :)
any help would be appreciated.
Local variable are captured in a compiler generated class at runtime, this explains the Payroll.Test.Administration.TestRepositories+<>c__DisplayClass13).searchKey part. To get the generated field's value in your expression you must explicitly replace it's value when visiting the expression:
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.MemberType == MemberTypes.Property)
{
var memberName = node.Member.Name;
PropertyInfo otherMember = _type.GetProperty(memberName);
var ncols = node.Member.GetCustomAttributes(typeof(NColumn), true);
if (ncols.Any())
{
var ncol = (NColumn)ncols.First();
otherMember = _type.GetProperty(ncol.Name);
}
var inner = Visit(node.Expression);
return Expression.Property(inner, otherMember);
}
if (node.Member.MemberType == MemberTypes.Field)
{
if (node.Expression is ConstantExpression)
{
var owner = ((ConstantExpression)node.Expression).Value;
var value = Expression.Constant(((FieldInfo)node.Member).GetValue(owner));
return value;
}
}
return base.VisitMember(node);
}

Getting sub property names strongly typed

With databinding objects to controls and grids I hated how the property names would be magic strings, so I created a very simple method as follows:
public static string GetPropertyName<PropertyType>(Expression<Func<T, PropertyType>> expressionForProperty)
{
MemberExpression expression = expressionForProperty.Body as MemberExpression;
return expression.Member.Name;
}
This lets me use code such as:
Product.GetPropertyName(m => m.Name)
to return "Name".
This works perfectly for basic objects. However if I change the method call to be:
Product.GetPropertyName(m => m.ProductCategory.Name)
This also returns "Name". But in order for the databinding to work, I would need it to return "ProductCategory.Name". Is there a way I can get to this by changing the method "GetPropertyName"?
A possible workaround would be to do this:
string test = Product.GetPropertyName(p => p.ProductCategory) + "." + ProductCategory.GetPropertyName(pc => pc.Name)
However, this isn't a neat solution.
This is a modified version of something I might have found here on StackOVerflow:
public static class GetPropertyNameExtension
{
public static string GetPropertyName<TArg, TProperty>(this Expression<Func<TArg, TProperty>> propertyExpression)
{
return propertyExpression.Body.GetMemberExpression().GetPropertyName();
}
public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> propertyExpression)
{
return propertyExpression.Body.GetMemberExpression().GetPropertyName();
}
public static string GetPropertyName(this MemberExpression memberExpression)
{
if (memberExpression == null)
{
return null;
}
if (memberExpression.Member.MemberType != MemberTypes.Property)
{
return null;
}
var child = memberExpression.Member.Name;
var parent = GetPropertyName(memberExpression.Expression.GetMemberExpression());
return parent == null ?
child
: parent + "." + child;
}
public static MemberExpression GetMemberExpression(this Expression expression)
{
if (expression is MemberExpression)
return (MemberExpression)expression;
if (expression is UnaryExpression)
return (MemberExpression)((UnaryExpression)expression).Operand;
return null;
}
}
I came up with the following which seems to work:
public static string GetComplexPropertyName<PropertyType>(Expression<Func<T, PropertyType>> expressionForProperty)
{
// get the expression body
Expression expressionBody = expressionForProperty.Body as MemberExpression;
string expressionAsString = null;
// all properties bar the root property will be "convert"
switch (expressionBody.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
UnaryExpression unaryExpression = expressionBody as UnaryExpression;
if (unaryExpression != null)
{
expressionAsString = unaryExpression.Operand.ToString();
}
break;
default:
expressionAsString = expressionBody.ToString();
break;
}
// makes ure we have got an expression
if (!String.IsNullOrWhiteSpace(expressionAsString))
{
// we want to get rid of the first operand as it will be the root type, so get the first occurence of "."
int positionOfFirstDot = expressionAsString.IndexOf('.');
if (positionOfFirstDot != -1)
{
return expressionAsString.Substring(positionOfFirstDot + 1, expressionAsString.Length - 1 - positionOfFirstDot);
}
}
return string.Empty;
}

Categories

Resources