как создать дерево выражений / лямбда-выражение для глубинного свойства из строки
-
22-08-2019 - |
Вопрос
Задана строка:"Person.Адрес.Почтовый индекс" Я хочу иметь возможность получить / установить это свойство почтового индекса для экземпляра Person.Как я могу это сделать?Моя идея состояла в том, чтобы разделить строку на "." и затем выполнить итерацию по частям, ища свойство предыдущего типа, затем создать дерево выражений, которое выглядело бы примерно так (прошу прощения за псевдосинтаксис):
(person => person.Address) address => address.Postcode
Однако у меня возникли реальные проблемы с созданием дерева выражений!Если это лучший способ, может кто-нибудь подсказать, как это сделать, или есть более простая альтернатива?
Спасибо
Эндрю
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public Address Address{ get; set; }
public Person()
{
Address = new Address();
}
}
public class Address
{
public string Postcode { get; set; }
}
Решение
Почему вы не используете рекурсию?Что - то вроде:
setProperyValue(obj, propertyName, value)
{
head, tail = propertyName.SplitByDotToHeadAndTail(); // Person.Address.Postcode => {head=Person, tail=Address.Postcode}
if(tail.Length == 0)
setPropertyValueUsingReflection(obj, head, value);
else
setPropertyValue(getPropertyValueUsingReflection(obj, head), tail, value); // recursion
}
Другие советы
Звучит так, как будто вы отсортированы с помощью обычного отражения, но для информации код для построения выражения для вложенных свойств был бы очень похож на этот порядок-по коду.
Обратите внимание, что для установки значения вам необходимо использовать GetSetMethod()
в свойстве и вызовите это - нет встроенного выражения для присвоения значений после построения (хотя это поддерживается в версии 4.0).
(редактировать) вот так:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class Foo
{
public Foo() { Bar = new Bar(); }
public Bar Bar { get; private set; }
}
class Bar
{
public string Name {get;set;}
}
static class Program
{
static void Main()
{
Foo foo = new Foo();
var setValue = BuildSet<Foo, string>("Bar.Name");
var getValue = BuildGet<Foo, string>("Bar.Name");
setValue(foo, "abc");
Console.WriteLine(getValue(foo));
}
static Action<T, TValue> BuildSet<T, TValue>(string property)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
ParameterExpression valArg = Expression.Parameter(typeof(TValue), "val");
Expression expr = arg;
foreach (string prop in props.Take(props.Length - 1))
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
// final property set...
PropertyInfo finalProp = type.GetProperty(props.Last());
MethodInfo setter = finalProp.GetSetMethod();
expr = Expression.Call(expr, setter, valArg);
return Expression.Lambda<Action<T, TValue>>(expr, arg, valArg).Compile();
}
static Func<T,TValue> BuildGet<T, TValue>(string property)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
return Expression.Lambda<Func<T, TValue>>(expr, arg).Compile();
}
}
Если кто-то заинтересован в компромиссе производительности между простое отражение подход (также хорошие примеры здесь и здесь) и Марка Построение выражения подходите...
Мой тест включал в себя получение относительно глубокого свойства (A.B.C.D.E) 10 000 раз.
- Простое отражение:64 мс
- Построение выражения:1684 мс
Очевидно, что это очень специфический тест, и я не рассматривал оптимизацию или настройку свойств, но я думаю, что стоит отметить увеличение производительности в 26 раз.
Вы хотите рассмотреть возможность предоставления вашего собственного propertydescriptor'а через TypeConverter или какой-либо другой источник.
Я реализовал именно то, что вы описали для текущего проекта (извините, коммерческого, иначе я бы поделился), используя BindingSource и предоставляя информацию через него.
Идея заключается в следующем:
Все, что вам нужно сделать, как только у вас будет тип, - это создать небольшие "стеки" для средств получения и установки свойств, которые вы можете собрать, пройдя сначала по дереву свойств типа и его свойствам в ширину, ограничив глубину указанным количеством уровней и удалив циклические ссылки в зависимости от ваших структур данных.
Я довольно успешно использую это с объектами Linq2SQL и в сочетании с их списками привязок :)
Дерево выражений
struct tree
{
char info;
struct tree *rchild;
struct tree *lchild;
};
int prec(char data);
typedef struct tree * node;
char pop_op();
node pop_num();
void push_op(char item);
node create()
{
return((node)malloc(sizeof(node)));
}
node num[20],root=NULL;
char op[20],oprt,ev[20];
int nt=-1,ot=-1,et=-1;
main()
{
node newnode,item,temp;
char str[50];
int i,k,p,s,flag=0;
printf("ENTER THE EXPRESSION ");
scanf("%s",str);
printf("\n%s",str);
for(i=0;str[i]!='\0';i++)
{
if(isalnum(str[i]))
{
newnode=create();
newnode->info=str[i];
newnode->lchild=NULL;
newnode->rchild=NULL;
item=newnode;
push_num(item);
}
else
{
if(ot!=-1)
p=prec(op[ot]);
else
p=0;
k=prec(str[i]);
if(k==5)
{
while(k!=1)
{
oprt=pop_op();
newnode=create();
newnode->info=oprt;
newnode->rchild=pop_num();
newnode->lchild=pop_num();
// if(root==NULL)
root=newnode;
// else if((newnode->rchild==root)||(newnode->lchild==root))
// root=newnode;
push_num(root);
k=prec(op[ot]);
}
oprt=pop_op();
}
else if(k==1)
push_op(str[i]);
else
{
if(k>p)
push_op(str[i]);
else
{
if(k<=p)
{
oprt=pop_op();
newnode=create();
newnode->rchild=pop_num();
newnode->lchild=pop_num();
if(root==NULL)
root=newnode;
else if((newnode->rchild==root)||(newnode->lchild==root))
root=newnode;
push_num(newnode);
push_op(str[i]);
// k=prec(op[ot]);
}
}
}
}
}
printf("\nThe prefix expression is\n ");
preorder(root);
printf("\nThe infix exp is\n ");
inorder(root);
printf("\nThe postfix expression is\n ");
postorder(root);
evaluate();
}
void push_op(char item)
{
op[++ot]=item;
}
push_num(node item)
{
num[++nt]=item;
}
char pop_op()
{
if(ot!=-1)
return(op[ot--]);
else
return(0);
}
node pop_num()
{
if(nt!=-1)
return(num[nt--]);
else
return(NULL);
}
int prec(char data)
{
switch(data)
{
case '(':return(1);
break;
case '+':
case '-':return(2);
break;
case '*':
case '/':return(3);
break;
case '^':return(4);
break;
case ')':return(5);
break;
}
}
inorder(node temp)
{
if(temp!=NULL)
{
inorder(temp->lchild);
printf("%c ",temp->info);
inorder(temp->rchild);
}
}
preorder(node temp)
{
if(temp!=NULL)
{
printf("%c ",temp->info);
preorder(temp->lchild);
preorder(temp->rchild);
}
}
postorder(node temp)
{
if(temp!=NULL)
{
postorder(temp->lchild);
postorder(temp->rchild);
printf("%c ",temp->info);
ev[++et]=temp->info;
}
}
evaluate()
{
int i,j=-1,a,b,ch[20];
for(i=0;ev[i]!='\0';i++)
{
if(isalnum(ev[i]))
ch[++j]=ev[i]-48;
else
{
b=ch[j];
a=ch[j-1];
switch(ev[i])
{
case '+':ch[--j]=a+b;
break;
case '-':ch[--j]=a-b;
break;
case '*':ch[--j]=a*b;
break;
case '/':ch[--j]=a/b;
break;
}
}
}
printf("\nValue = %d",ch[0]);
}