11 Stimmen

Wie erstellt man einen Ausdrucksbaum / Lambda für eine tiefe Eigenschaft aus einer Zeichenkette?

Gegeben eine Zeichenkette: "Person.Address.Postcode" möchte ich diese Postleitzahleneigenschaft in einer Instanz von Person abrufen/einstellen können. Wie kann ich das tun? Meine Idee war es, die Zeichenkette durch "." zu teilen und dann über die Teile zu iterieren und nach der Eigenschaft des vorherigen Typs zu suchen, dann einen Ausdrucksbaum zu erstellen, der in etwa so aussehen würde (Entschuldigung für die Pseudosyntax):

(person => person.Address) address => address.Postcode

Ich habe allerdings echte Schwierigkeiten, den Ausdrucksbaum tatsächlich zu erstellen! Wenn dies der beste Weg ist, kann jemand vorschlagen, wie man darüber gehen, oder gibt es eine einfachere Alternative?

Danke

Andrew

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

22voto

Marc Gravell Punkte 970173

Es klingt wie Sie mit regulären Reflexion sortiert sind, aber zur Info, der Code zum Erstellen eines Ausdrucks für verschachtelte Eigenschaften wäre sehr ähnlich zu dieser Code für die Bestellung .

Beachten Sie, dass Sie, um einen Wert zu setzen, die Funktion GetSetMethod() auf die Eigenschaft und rufen diese auf - es gibt keinen eingebauten Ausdruck für die Zuweisung von Werten nach der Konstruktion (obwohl es unterstützt in 4.0 ).

(Bearbeiten) wie folgt:

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

0 Stimmen

Ich habe gerade nach einem ähnlichen Thema gesucht und dies erschien als erstes bei Google und beantwortete meine Frage :D

0 Stimmen

Funktioniert gut - aber wenn die Leistung ein Problem ist, siehe stackoverflow.com/a/14708196/188926

3voto

Konstantin Savelev Punkte 433

Warum verwenden Sie keine Rekursion? Etwas wie:

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
}

0 Stimmen

Ich versuche immer, die Dinge zu sehr zu vereinfachen. ich werde es versuchen, danke.

0 Stimmen

Denken Sie nur daran, dass C# nicht tail-recursive ist, so dass Sie mit StackOverflow Ausnahme enden könnte.

2voto

Dunc Punkte 17324

Wenn Sie sich für den Leistungsvergleich zwischen dem einfache Reflexion Ansatz (auch schöne Beispiele aquí y aquí ) und Marcs Ausdrucksbildende Maßnahmen Annäherung...

Mein Test bestand darin, eine relativ tiefe Eigenschaft (A.B.C.D.E) 10.000 Mal abzurufen.

  1. Einfache Überlegung: 64 ms
  2. Ausdrucksbildung: 1684 ms

Natürlich ist dies ein sehr spezifischer Test, und ich habe keine Optimierungen oder Einstellungseigenschaften berücksichtigt, aber ich denke, ein 26-facher Leistungsabfall ist erwähnenswert.

2 Stimmen

In diesem Szenario gibt es auch andere Wege; ein Func<,> erstellt über ILGenerator kann sehr schnell sein, solange sie zwischengespeichert wird und nicht bei jedem Aufruf neu erstellt wird

1 Stimmen

@Marc Stimmt, es gibt definitiv Caching-Potenzial, und dieser Test ist sehr primitiv, dass es nur blind ruft die BuildGet-Methode 1000's von Zeiten. Ich denke, es ist nur eine Warnung für copy-pasters (wie mich!), die die schnellste OOTB-Lösung benötigen.

0 Stimmen

Ich habe ähnliche Ergebnisse in LinqPad erhalten. gist.github.com/zaus/6884806 Ich dachte, es könnte helfen, wenn der Ausdruck nicht die ganze PropertyInfo aber das ist nicht der Fall (es sieht nur "sauberer" aus)

1voto

leppie Punkte 111830

Sie möchten Ihre eigenen PropertyDescriptor's über TypeConverter oder eine andere Quelle bereitstellen.

Ich habe genau das implementiert, was Sie für das aktuelle Projekt beschreiben (sorry, kommerziell, sonst würde ich teilen), durch Ableitung von BindingSource, und die Bereitstellung der Informationen über dort.

Die Idee ist die folgende:

Alles, was Sie tun müssen, ist, sobald Sie den Typ haben, kleine "Stapel" für die Getter und Setter von Eigenschaften zu erstellen, und diese können Sie sammeln, indem Sie den Eigenschaftsbaum des Typs und seiner Eigenschaften zuerst in der Breite durchgehen, die Tiefe auf eine bestimmte Anzahl von Ebenen begrenzen und zirkuläre Referenzen je nach Ihren Datenstrukturen entfernen.

Ich verwende dies recht erfolgreich mit Linq2SQL-Objekten und in Kombination mit deren Bindungslisten :)

-10voto

Ausdrucksbaum

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X