【问题标题】:Convert LambdaExpression to String, Including Values将 LambdaExpression 转换为字符串,包括值
【发布时间】:2013-03-18 14:02:34
【问题描述】:

我有一个将 LambdaExpression 转换为字符串的方法。我将这些字符串用作缓存的键。

string p = "x";
var a = LambdaToString<MyType>(m => m.P == p);

与此不同:

string p = "y";
var a = LambdaToString<MyType>(m => m.P == p);

但是,无论 p 的值如何,我的 LambdaToString 方法的当前状态都会产生相同的输出。即:

(MyType.P == value(ConsoleApplication1.Program+<>c__DisplayClass0).p)

我希望我的 LambdaToString 函数做的是将表达式的“value(class).p”部分解析为实际的文字字符串“x”或“y”,视情况而定。

这是我的 LambdaToString 方法的当前状态。我不确定我需要做什么来修改它以产生我想要的输出:

    public static string LambdaToString<T>(Expression<Func<T, bool>> expression)
    {
        string body = expression.Body.ToString();

        foreach (var parm in expression.Parameters)
        {
            var parmName = parm.Name;
            var parmTypeName = parm.Type.Name;
            body = body.Replace(parmName + ".", parmTypeName + ".");
        }

        return body;
    }

【问题讨论】:

  • 当然,如果pconst 变量,这将自动发生(该变量不会被“捕获”和“提升”到生成的类上的字段,如&lt;&gt;c__DisplayClass0然后),但我想你从某个地方得到p,所以它不能被声明为const(所以我的评论可能没有用)。

标签: c# linq


【解决方案1】:

我将这些字符串用作缓存的键。

在很多情况下它是不正确的,即使它适用于您的项目。使用Expression.ToString() 作为密钥可以很容易地被以下方法打败:

//counter-example 1
Expression<Func<string, bool>> exp1 = s => s == "a";
Expression<Func<string, bool>> exp2 = ss => ss == "a";
//the two will be considered different in your cache solution
//but they are essentially the same, well that's not the worst, see next

//counter-example 2
Expression<Func<int, bool>> exp3 = i => i > 10;
Expression<Func<long, bool>> exp4 = i => i > 10;
//the two will be considered the same in your cache solution
//of course they are different, probably hences runtime exceptions

以上根本不是答案。如果你不关心这个,让我们继续基于“使用字符串作为键”。

您希望缓存表达式,但要识别那些看起来相同且包含常量的表达式。那为什么不用表达式+常量来构建密钥呢?在您的示例代码中,键将是:

"m => m.P == p @@SPECIAL_SEPERATOR@@ x"
"m => m.P == p @@SPECIAL_SEPERATOR@@ y"

是的,如果一个常量包含像“@@SPECIAL_SEPERATOR@@”这样的值,那么一切都会崩溃。这从一开始就不是一个严格的解决方案,因为您选择字符串作为缓存键。

如果您决定选择其他缓存方法,请查看this

【讨论】:

    【解决方案2】:

    好吧,要获得 p 值,您可以这样做(可能更简单、更可靠的方式来做到这一点,但是)。

    public static string LambdaToString<T>(Expression<Func<T, bool>> expression)
        {
            BinaryExpression binaryExpression = expression.Body as BinaryExpression;
            Expression right = binaryExpression.Right;//right part of the "==" of your predicate
            var objectMember = Expression.Convert(right, typeof(object));//convert to object, as we don't know what's in
    
            var getterLambda = Expression.Lambda<Func<object>>(objectMember);
    
            var getter = getterLambda.Compile();
    
    
    
            var valueYouWant = getter();//here's the "x" or "y"
            //...
    

    或更短

    Expression right = (expression.Body as BinaryExpression).Right;
    var valueYouWant = Expression.Lambda(right).Compile().DynamicInvoke();
    

    注意

    当然,这并不适合很多场景,它只是了解如何获得价值的基础。如果您的谓词是

    ,它将不起作用
    var x = 1;
    var y = 2;
    var result = LambdaToString<YourType>(v => v.A== x && v.B == y)
    

    【讨论】:

      【解决方案3】:

      这是我的答案。理想情况下,这将能够处理向它抛出的任何可能的表达式。现在它很可能没有,但它处理了我在测试中扔给它的所有简单、常见的事情。

      如果您想出一个无法处理的示例,请将其留在 cmets 中,我会尝试修改函数来处理它。

          public static string LambdaToString<T>(Expression<Func<T, bool>> expression)
          {
      
              var replacements = new Dictionary<string, string>();
              WalkExpression(replacements, expression);
      
      
              string body = expression.Body.ToString();
      
              foreach (var parm in expression.Parameters)
              {
                  var parmName = parm.Name;
                  var parmTypeName = parm.Type.Name;
                  body = body.Replace(parmName + ".", parmTypeName + ".");
              }
      
              foreach (var replacement in replacements)
              {
                  body = body.Replace(replacement.Key, replacement.Value);    
              }
      
              return body;
          }
      
          private static void WalkExpression(Dictionary<string, string> replacements, Expression expression)
          {
              switch (expression.NodeType)
              {
                  case ExpressionType.MemberAccess:
                      string replacementExpression = expression.ToString();
                      if (replacementExpression.Contains("value("))
                      {
                          string replacementValue = Expression.Lambda(expression).Compile().DynamicInvoke().ToString();
                          if (!replacements.ContainsKey(replacementExpression))
                          {
                              replacements.Add(replacementExpression, replacementValue.ToString());
                          }
                      }
                      break;
      
                  case ExpressionType.GreaterThan:
                  case ExpressionType.GreaterThanOrEqual:
                  case ExpressionType.LessThan:
                  case ExpressionType.LessThanOrEqual:
                  case ExpressionType.OrElse:
                  case ExpressionType.AndAlso:
                  case ExpressionType.Equal:
                      var bexp = expression as BinaryExpression;
                      WalkExpression(replacements, bexp.Left);
                      WalkExpression(replacements, bexp.Right);
                      break;
      
                  case ExpressionType.Call:
                      var mcexp = expression as MethodCallExpression;
                      foreach (var argument in mcexp.Arguments)
                      {
                          WalkExpression(replacements, argument);
                      }
                      break;
      
                  case ExpressionType.Lambda:
                      var lexp = expression as LambdaExpression;
                      WalkExpression(replacements, lexp.Body);
                      break;
      
                  case ExpressionType.Constant:
                      //do nothing
                      break;
      
                  default:
                      Trace.WriteLine("Unknown type");
                      break;
              }
      

      【讨论】:

      • 不会处理你有两个同名但不同类的静态方法的场景。例如。 () =&gt; Class1.StaticM() + Class2.StaticM() 序列化为 "StaticM() + StaticM()"
      • 这似乎也调用了表达式来解析可能不太理想的输入值。
      【解决方案4】:

      非常快速和肮脏的解决方案是传递参数名称及其值并替换它。

      public static string LambdaToString<T>(Expression<Func<T, bool>> expression, string value,string paramName )
              {
      
                  string body = expression.Body.ToString().Replace(paramName,value);
      
      
                  foreach (var parm in expression.Parameters)
                  {
                      var parmName = parm.Name;
      
                      var parmTypeName = parm.Type.Name;
                      body = body.Replace(parmName + ".", parmTypeName + ".");
                  }
      
                  return body;
              }
      

      【讨论】:

        猜你喜欢
        • 2017-03-22
        • 2017-08-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多