本帖最后由 Rightcode 于 2013-02-23 18:00:34 编辑

解决方案 »

  1.   

    本帖最后由 caozhy 于 2013-02-23 20:11:58 编辑
      

  2.   

     public bool GetResult(Expression<Func<User, bool>> expression, User user)
            {
                return expression.Compile()(user);
            }
      

  3.   

    晕。看看你所写的需求用例private void button1_Click(object sender, EventArgs e)
    {
        User user = new User();
        user.Name = "David";
        user.Age = 12;
     
        bool rst = GetResult(p => p.Age > 10);
    }这里的user对于后边的计算毫无用处吧!那么就可以删除掉。剩下private void button1_Click(object sender, EventArgs e)
    {
        bool rst = GetResult(p => p.Age > 10);
    }你现在还坚持“PS:请别绕开这个方法,这只是个例子,主要....”这类问题方式吗?
      

  4.   

    楼上几位朋友,不好意思,我写错了,代码应该是这样public class User
    {
    public string Name { get; set; }
    public int Age { set; get; }
    }private void button1_Click(object sender, EventArgs e)
    {
    User user = new User();
    user.Name = "David";
    user.Age = 12; DoSomething(p => p.Age == user.Age);
    }public void DoSomething(Expression<Func<User, bool>> expression)
    {
    //获取user.Age的值,也就是12
    //int age = ...
    }主要是不把user直接作为DoSomething的方法传进去,而是通过从拉姆达表达式中提取值,有没有什么方法?
      

  5.   

    看来你根本不懂Lambda表达式,以及委托。委托是调用者(Main方法)将一个函数实现(expression)作为参数传给被调用者(DoSomething),由被调用者而不是调用者触发,换言之,DoSomething决定了给这个Lambda传递什么User,而不是说让调用者决定是什么User。你问DoSomething如何获取委托中的User参数是什么,就好像问Main函数中应该给DoSomething什么作为expression参数一样,答案是管不着,想让它是什么就是什么。(函数本身管不了调用者给它什么参数)
      

  6.   

    附上ConditionEnumerator方法内容,这个还是caozhy你教我的。private static IEnumerable<Expression> ConditionEnumerator(Expression expression)
    {
    yield return expression;
    switch (expression.NodeType)
    {
    case ExpressionType.Lambda:
    foreach (var item in ConditionEnumerator((expression as LambdaExpression).Body))
    yield return item;
    break;
    case ExpressionType.And:
    case ExpressionType.AndAlso:
    case ExpressionType.Or:
    case ExpressionType.OrElse:
    foreach (var item in ConditionEnumerator((expression as BinaryExpression).Left))
    yield return item;
    foreach (var item in ConditionEnumerator((expression as BinaryExpression).Right))
    yield return item;
    break;
    case ExpressionType.Equal:
    case ExpressionType.GreaterThan:
    case ExpressionType.GreaterThanOrEqual:
    case ExpressionType.LessThan:
    case ExpressionType.LessThanOrEqual:
    foreach (var item in ConditionEnumerator((expression as BinaryExpression).Left))
    yield return item;
    foreach (var item in ConditionEnumerator((expression as BinaryExpression).Right))
    yield return item;
    break;
    }
    }
      

  7.   

    这是闭包捕获的变量,我简单解释下,C#编译器会自动生成一个类,包装Lambda表达式对外部的捕获(Main函数的上下文变量),你可以通过反编译看到这一点。你不应该在调用者去操作这些捕获变量,你的设计完全是不合理的。另外捕获变量(user)和Lambda变量(p)是完全不同的。实际上你已经改了1L的题目了。捕获变量还是可以获取的,就你这个特定的需求,我可以写一段代码给你:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;namespace ConsoleApplication1
    {
        public class User
        {
            public string Name { get; set; }
            public int Age { set; get; }
        }    class Program
        {
            static void Main(string[] args)
            {
                User user = new User();
                user.Name = "David";
                user.Age = 12;
                DoSomething(p => p.Age == user.Age);
            }
            static void DoSomething(Expression<Func<User, bool>> expression)
            {
                var exp = (((expression.Body) as BinaryExpression).Right as MemberExpression).Expression;
                Console.WriteLine((Expression.Lambda(exp).Compile().DynamicInvoke() as User).Age);
            }
        }
    }注意,捕获变量相当于在两个上下文共享的变量,因此它不能被视作一个常数。
      

  8.   

    我总觉得我没有说清楚。我还是用代码来说明捕获的机制吧,你自己体会下,在没有搞清楚原理的时候不要盲目按照你的想象搞还是上面的代码,等价类似如下的代码,编译器会产生一个名字类似叫displayclass<>0之类的匿名类,在这里,我用ClosureContext代替了编译器产生的那个,作为一个常数表达式传入Lambda,这样达到了在两个上下文中共享变量的功能。在这里,我没有展示它的复杂性,因为user是引用变量,值类型不是简单赋值就可以的。因此这只是一个粗糙的模拟。但是这足以回答你的问题,为什么p不能获取,而user可以。我希望你仔细看懂这些代码。using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;namespace ConsoleApplication1
    {
        public class User
        {
            public string Name { get; set; }
            public int Age { set; get; }
        }    class ClosureContext
        {
            public User user { get; set; }
        }
        class Program
        {
            static void Main(string[] args)
            {
                User user = new User();
                user.Name = "David";
                user.Age = 12;
                ClosureContext cc = new ClosureContext();
                cc.user = user;
                var param = Expression.Parameter(typeof(User));
                var lambda = Expression.Lambda(Expression.Equal(Expression.MakeMemberAccess(param, typeof(User).GetProperty("Age")), Expression.MakeMemberAccess(Expression.MakeMemberAccess(Expression.Constant(cc, typeof(ClosureContext)), typeof(ClosureContext).GetProperty("user")), typeof(User).GetProperty("Age"))), param);
                DoSomething(lambda as Expression<Func<User, bool>>);
                Console.WriteLine(lambda.Compile().DynamicInvoke(new User() { Age = 12 }));
                Console.WriteLine(lambda.Compile().DynamicInvoke(new User() { Age = 13 }));
            }
            static void DoSomething(Expression<Func<User, bool>> expression)
            {
                var exp = (((expression.Body) as BinaryExpression).Right as MemberExpression).Expression;
                Console.WriteLine((Expression.Lambda(exp).Compile().DynamicInvoke() as User).Age);
            }
        }
    }
      

  9.   

    我仔细看过了你的代码,也顺带看了你的博客,貌似你是微软的MVP?仰慕!
    你说的没错,p不能获取,而user可以,但这是因为我题目写错了,我就是想获取user的。拉姆达表达式、委托等的功能确实如你所说的那样,但我这里是一个特定的情景下所用的,我的原意是:
    我们框架中,每个数据库表都有个对应的类,例如:userDal,用于做基础的操作。
    最开始,我们调用的方式是:
    userDal.GetList("Age = 12")     //参数中的字符串,会传到SQL存储过程中,作为WHERE语句,组装成为完整的SQL语句:SELECT * FROM User WHERE Age = 12
    但后来,我们想让调用能够优雅一些,或者说,可以让IDE自动检查拼写,减少一些无意的错误,这样说比较实在,我们希望可以类似这么调用:
    userDal.GetList(p=>p.Age == 12)  
    然后在GetList里,将其转化为WHERE字符串,这样一来,至少要考虑以下几种情况:
    userDal.GetList(p=>p.Age == 12)  //右值是常量
    userDal.GetList(p=>p.Age == age)  //右值是变量
    userDal.GetList(p=>p.Age == user.Age)  //右值是对象
    其它的例如userDal.GetList(p=>p.Age == getAge())右值是方法之类的,就暂时不考虑了,先不要弄得那么复杂。我会让他们不要用这些方式调用,调了的话,直接抛个异常出来就行。
    适用就好。
    (不需要担心这些Dal类的繁琐问题,这些都由脚本自动生成)
    于是就有了上一篇帖子(http://bbs.csdn.net/topics/390344793,你也用心做了回答),以及今天这篇帖子。刚才看到你的回复之前,我也已经写出来了,关键的一句是exp.GetProperty("Expression").GetProperty("Expression").GetProperty("Value").GetPrivateField(“user”)另外配套了2个扩展方法public static object GetProperty(this object obj, string propertyName)
    {
    return obj.GetType().GetProperty(propertyName).GetValue(obj, null);
    }
    public static object GetPrivateField(this object obj, string fieldName)
    {
    return obj.GetType().GetField(fieldName).GetValue(obj);
    }
    当然你的方法更好一些,我的方法调用了私有属性,比较危险。我一会改成你这种,谢谢你无私的帮助,还有这么晚,作为程序员,还是少熬夜好,我其实已经很少写程序了,这两次帖子都是刚好我有时间,想着优化一下框架。你也早点休息!
      

  10.   

    p哪里来?它是你的委托的一个参数,但是你的 button1_Click 中根本没有它的原始对象,这也是一个根本不合语法的需求啊!
      

  11.   

    在你说明了 user 的用处之后,你仍然没有能够明确地描述需求。我建议你先把它写成一个接口比较完备——可实现——的需求,例如private void button1_Click(object sender, EventArgs e)
    {
        User user = new User();
        user.Name = "David";
        user.Age = 12;
     
        bool rst = GetResult(() => user.Age > 10);
    }实际上能够用代码来描述需求,是个核心技术。这个部分不准确,就一味地去动手实现它了,往往纠结好多天。很多程序员看似很忙其实整天在那里“不知道想什么”,往往就是因为他们没有能在动手实现之前先养成准确地写出测试代码(而不是实现)的习惯。
      

  12.   

    看了你在#10楼的描述,发现你根本没有在整个描述中说明所谓 UserDal 的机制,所以造成了问题。如果你的需求“太大”也就必然太囧,你需要先写出一个关于 UserDal 的东西来,然后在 GetResult 中调用它。如果你不先说明,没人知道 UserDal 它的接口是什么!也就不知道 GetResult 中怎么写。告诉你一件基本的开发常识。只有那些空东西谈传统的“需求分析”概念的人才喜欢凡事都“自顶向下、层层分解”。真正好的软件设计师,先有核心概念,然后串起来,既不是自顶向下分解的也不是自顶向上去拼凑的思路。因此遇到瓶颈一段时间自己就能先发现自己表达上的问题,而不是满脑子是技术术语,才是好的技术设计人员。
      

  13.   

    这里能写的东西有限,写得再完整,也不可能写太多,反而分散了看帖者的注意力。
    这样说吧,首先模型定义:public class User
    {
        public string Name { get; set; }
        public int Age { set; get; }
    }这个是调用者private void button1_Click(object sender, EventArgs e)
    {
        User user = new User();
        user.Name = "David";
        user.Age = 12;    int age = 12;
     
        int newAge1 = GetNewAge(p => p.Age == user.Age);
        int newAge2 = GetNewAge(p => p.Age == age);
        int newAge3 = GetNewAge(p => p.Age == 12);    // 正确执行完之后,newAge1,newAge2,newAge3的值都应该为15.
    }这个是被调用的方法,也是我的疑问所在// 这个方法的作用是将传进来拉姆达表达式右侧的值加3,然后返回去
    public int GetNewAge(Expression<Func<User, bool>> expression)
    {
        // 如何实现
    }
      

  14.   

    楼上的帖子,sp1234你觉得描述得清楚了吗,呵呵。
    其实经过昨晚的努力和caozhy的提点,我已经完成了,这个问题的意义在于如何从C#语法闭包中取得上下文的值,可能在做类ORM项目的场合会用到,我们目前是应用在我们公司的一个基础框架中,我把我完成的结果贴出来,以备有同样问题的人参考:// 这个方法的作用是将传进来拉姆达表达式右侧的值加3,然后返回去
    public int GetNewAge(Expression<Func<User, bool>> expression)
    {
    return (int)Expression.Lambda(((expression as LambdaExpression).Body as BinaryExpression).Right).Compile().DynamicInvoke() + 3;
    }
      

  15.   

    按照这个方法改造,现在已经能够通过这种方式,实现将传进来的拉姆达表达式直接转化为SQL语句的功能,代码就不贴了,精华部分在上楼那个一句话代码里。
    可以说已经达成第一个帖子的目的了,有这方面遇到同样问题的可以跟帖,我会尽量回答(http://bbs.csdn.net/topics/390344793)另外不得不说,很多人之所以会上来发帖,是因为这里氛围相对较好,大家也热心,抱着一种珍惜的心态来这里。可有些人倒好,也许指点江山习惯了,根本就没看清楚你问题的需求,就开始来了,说一大堆不着边际的话,完了问题还没解决,我撞豆腐的心都有了。麻烦你看清楚问题的需求,即使发帖人表达得不是非常精确,也尽量去理解作者原意。现在网络发达,一般简单和常见的问题都是谷歌百度,肯定只是在某个很细小的点卡住才上来问,不要总是希望绕过问题,这样并不能真正解决问题。我就发了两次贴,一次被说南郭先生,一次被说没有常识,我无语。好在我大学毕业到现在也做了7、8年开发了,脸皮还是有点厚度,其他新手我就不不敢保证了。