comment créer un arbre d'expression/lambda pour une propriété profonde à partir d'une chaîne
-
22-08-2019 - |
Question
Étant donné une chaîne :"Person.Address.Postcode" Je souhaite pouvoir obtenir/définir cette propriété de code postal sur une instance de Person.Comment puis-je faire ceci?Mon idée était de diviser la chaîne par « . » puis d’itérer sur les parties, en recherchant la propriété sur le type précédent, puis de construire une arborescence d’expression qui ressemblerait à quelque chose comme (désolé pour la pseudo syntaxe) :
(person => person.Address) address => address.Postcode
Cependant, j'ai vraiment du mal à créer l'arborescence d'expression !Si c'est la meilleure solution, quelqu'un peut-il suggérer comment procéder, ou existe-t-il une alternative plus simple ?
Merci
André
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; }
}
La solution
Pourquoi ne pas utiliser la récursivité? Quelque chose comme:
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
}
Autres conseils
On dirait que vous êtes trié avec une réflexion régulière, mais pour information, le code permettant de créer une expression pour les propriétés imbriquées serait très similaire à ce code de commande.
Notez que pour définir une valeur, vous devez utiliser GetSetMethod()
sur la propriété et invoquez-la - il n'y a pas d'expression intégrée pour attribuer des valeurs après la construction (bien que ce soit pris en charge dans 4.0).
(modifier) comme ceci :
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();
}
}
Si quelqu'un est intéressé par le compromis de performance entre le simple approche (aussi des beaux exemples here et ) et de Marc approche ...
Mon test consistait à obtenir une propriété relativement profonde (A.B.C.D.E) 10.000 fois.
- réflexion simple: 64 ms
- Expression construction: 1684 ms
Il est évident que ceci est un test très spécifique, et je n'ai pas considéré ou Optimisations la définition des propriétés, mais je pense un coup de performance 26x est à noter.
Vous voulez regarder à fournir votre propre de PropertyDescriptor via TypeConverter ou d'une autre source.
Je l'ai mis en œuvre exactement ce que vous décrivez pour le projet en cours (désolé, commercial, sinon je partagerais), en dérivant de BindingSource, et en fournissant les informations par là.
L'idée est la suivante:
Tout ce que vous devez faire est, une fois que vous avez le type est de créer peu « empile » pour le getter et setters de propriétés, et ceux que vous pouvez gagner par la marche de l'arbre de la propriété du type et ses propriétés en largeur d'abord, ce qui limite les profondeurs à un certain nombre de niveaux et la suppression des références circulaires en fonction de vos structures de données.
J'utilise ce succès avec des objets tout à fait linq2sql et en combinaison avec leurs listes de liaison:)
Arbre d'expression
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]);
}