Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert LambdaExpression to String, Including Values

Tags:

c#

linq

I have a method which converts a LambdaExpression to a string. I use these strings as keys for a cache.

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

is different from this:

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

However, the current state of my LambdaToString method is producing the same output regardless of the value of p. Which is:

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

What I would like my LambdaToString function to do is to resolve the "value(class).p" portion of the expression into the actual literal string of "x" or "y" as the case may be.

Here is the current state of my LambdaToString method. I am not sure what I would need to do to modify it to produce the outputs I want:

    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;
    }
like image 507
CleverPatrick Avatar asked Feb 17 '23 03:02

CleverPatrick


2 Answers

I use these strings as keys for a cache.

It's incorrect in a lot of circumstances, even it works in your project. Using Expression.ToString() as keys can be defeated easily by:

//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

The above is not an answer at all. If you don't care about that, let's continue based on "using strings as keys".

You want to cache the expressions, but identify those look-same expressions with constants in them. Then why not build the key with expression+constant? In your example code, the keys will be:

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

and yes, if one constant contains values like "@@SPECIAL_SEPERATOR@@", everything is going to crash. This is not a rigorous solution from the very beginning, because you choose strings as the cache key.

If you decide to choose another cache approach, check this.

like image 87
Cheng Chen Avatar answered Feb 19 '23 17:02

Cheng Chen


Well, to get p value, you could do (probably easier and more robust way to do this, but).

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"
        //...

or shorter

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

CAUTION

Of course, this won't fit a lot of scenarii, it's just the basic to understand how to get a value. It won't work if your predicate is

var x = 1;
var y = 2;
var result = LambdaToString<YourType>(v => v.A== x && v.B == y)
like image 29
Raphaël Althaus Avatar answered Feb 19 '23 19:02

Raphaël Althaus