Reputation: 9483
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;
}
Upvotes: 4
Views: 4721
Reputation: 43523
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.
Upvotes: 4
Reputation: 9483
Here is my answer. Ideally this would be able to handle any possible Expression that is thrown at it. Right now it most likely does not, but it handled all the simple, common things I threw at it in my tests.
If you come up with an example this doesn't handle, leave it in the comments and I will try to modify the function to handle it.
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;
}
Upvotes: 2
Reputation: 17556
very quick and dirty solution would be to pass the parameter name and its value and just replace it.
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;
}
Upvotes: 1
Reputation: 60493
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)
Upvotes: 2