如何从字符串为深层属性创建表达式树/lambda

Posted

技术标签:

【中文标题】如何从字符串为深层属性创建表达式树/lambda【英文标题】:how to create expression tree / lambda for a deep property from a string 【发布时间】:2010-10-06 22:06:01 【问题描述】:

给定一个字符串:“Person.Address.Postcode”,我希望能够在 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; 

【问题讨论】:

【参考方案1】:

听起来您是使用正则反射进行排序的,但对于信息,为嵌套属性构建表达式的代码与this order-by code 非常相似。

请注意,要设置一个值,您需要在属性上使用 GetSetMethod() 并调用它 - 没有用于在构造后分配值的内置表达式(尽管它是 supported in 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();
    

【讨论】:

刚刚搜索了一个类似的主题,这首先出现在谷歌上,并回答了我的问题:D 效果很好 - 但如果性能是一个问题,请参阅***.com/a/14708196/188926【参考方案2】:

为什么不使用递归?比如:

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

【讨论】:

我总是试图过度设计事物。把事情简单化!我试试吧,ta 请记住,C# 不是尾递归的,因此您最终可能会遇到 *** 异常。【参考方案3】:

如果有人对simple reflection 方法(还有很好的例子here 和here)和Marc 的Expression-building 方法之间的性能权衡感兴趣...

我的测试涉及获得相对较深的属性 (A.B.C.D.E) 10,000 次。

    简单反射:64 毫秒 表达构建:1684 毫秒

显然,这是一个非常具体的测试,我没有考虑过优化或设置属性,但我认为 26 倍的性能提升是值得注意的。

【讨论】:

这种情况下还有其他路线;通过ILGenerator 创建的Func&lt;,&gt; 可以非常快,只要它被缓存而不是每次调用都重新创建 @Marc 同意,肯定有缓存潜力,而且这个测试非常原始,因为它只是盲目地调用 BuildGet 方法 1000 次。我想这只是对需要最快 OOTB 解决方案的复制粘贴者(比如我!)的警告。 我在 LinqPad 中得到了类似的结果 -- gist.github.com/zaus/6884806;我认为如果表达式没有得到整个 PropertyInfo 可能会有所帮助,但它没有(只是看起来“更干净”)【参考方案4】:

您希望通过 TypeConverter 或其他来源提供您自己的 PropertyDescriptor。

通过从 BindingSource 派生并通过那里提供信息,我已经完全实现了您为当前项目描述的内容(对不起,商业,否则我会分享)。

思路如下:

所有你需要做的是,一旦你有了类型,就是为属性的 getter 和 setter 创建小的“堆栈”,你可以通过首先遍历类型的属性树及其属性广度来收集这些堆栈,限制深度到指定数量的级别并根据您的数据结构删除循环引用。

我在 Linq2SQL 对象以及它们的绑定列表中非常成功地使用了它:)

【讨论】:

【参考方案5】:

表达式树

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

【讨论】:

以上是关于如何从字符串为深层属性创建表达式树/lambda的主要内容,如果未能解决你的问题,请参考以下文章

用lambda表达式树优化反射

C# 表达式树 创建生成使用lambda转成表达式树~表达式树的知识详解

构建动态表达式树以过滤集合属性

从另外两个创建动态表达式 lambda(链接表达式)

如何使用 lambda 表达式和匿名类型获取类型的属性名称?

带有语句体的 lambda 表达式无法转换为 nopCommerce 中的表达式树 [重复]