Replacing parameters in a lambda expression
Asked Answered
A

2

8

I had a part of code that takes in lambda expressions at runtime, which I can then compile and invoke.

Something thing;

Expression<Action<Something>> expression = (c => c.DoWork());
Delegate del = expression.Compile();
del.DynamicInvoke(thing);

In order to save execution time, I stored those compiled delegates in a cache, a Dictionary<String, Delegate> which the key is the lambda expression string.

cache.Add("(Something)c => c.DoWork()", del);

For exact same calls, it worked fine. However I realized that I could receive equivalent lambdas, such as "d => d.DoWork()", which I should actually use the same delegate for, and I wasn't.

This got me wondering if there was a clean way (read "not using String.Replace", I already did that as a temporary fix) to replace the elements in a lambda expression, like maybe replacing them by arg0 so that both

(c => c.DoWork()) and (d => d.DoWork())

are transformed and compared as (arg0 => arg0.DoWork()) by using something fuctionnally similar to injecting a Expression.Parameter(Type, Name) in a lambda.

Is that possible ? (Answers can include C#4.0)

Administrative answered 20/1, 2010 at 19:53 Comment(2)
Will the key always be in the format: (type)arg => arg.Method() or more complex formats are expected?Paderewski
Well, the lambdas received could have more params, like (f,g) => f.Method(g), and I will have the types of f and g. The key is quite mundane actually and isn't set in stone, any suitable way to represent equivalence between 2 lambdas beside ((T)arg0,(V)arg1) => arg0.Method(arg1) could do the trick as the key.Administrative
I
3

I used strings, since it was the easisest way for me. You can't manually change the name of the parameter expression (it has the "Name" property, but it is read-only), so you must construct a new expression from pieces. What I did is created a "nameless" parameter (actually, it gets an autogenerated name in this case, which is "Param_0") and then created a new expression almost the same as the old one, but using the new parameter.

public static void Main()
{
    String thing = "test";

    Expression<Action<String>> expression = c => c.ToUpper();
    Delegate del = expression.Compile();
    del.DynamicInvoke(thing);

    Dictionary<String, Delegate> cache = new Dictionary<String, Delegate>();
    cache.Add(GenerateKey(expression), del);

    Expression<Action<String>> expression1 = d => d.ToUpper();
    var test = cache.ContainsKey(GenerateKey(expression1));
    Console.WriteLine(test);

}

public static string GenerateKey(Expression<Action<String>> expr)
{
    ParameterExpression newParam = Expression.Parameter(expr.Parameters[0].Type);
    Expression newExprBody = Expression.Call(newParam, ((MethodCallExpression)expr.Body).Method);
    Expression<Action<String>> newExpr = Expression.Lambda<Action<String>>(newExprBody, newParam);
    return newExpr.ToString();
}
Ito answered 20/1, 2010 at 20:35 Comment(2)
In the end I did fall back to something similar :) But I'm still looking for a more representative way to compare lambda-expressionsAdministrative
@Alexandra I'm trying to construct a new MethodCallExpression from an existing MethodCallExpression using the same body expression but with a different type argument. Could one accomplish this using something similar to your GenerateKey method? See: #14363887Tyrosine
A
3

There's more to a lambda expression than just the text. Lambdas are re-written by the compiler into something much more complicated. For example, they may close over variables (which could include other delegates). This means that two lambdas could look exactly the same, but perform completely different actions.

So you might have luck caching your compiled expression, but you need to give them a more meaningful name for the key than just the text of the expression. Otherwise no amount of argument substitution will help you.

Afterpiece answered 20/1, 2010 at 20:4 Comment(0)
I
3

I used strings, since it was the easisest way for me. You can't manually change the name of the parameter expression (it has the "Name" property, but it is read-only), so you must construct a new expression from pieces. What I did is created a "nameless" parameter (actually, it gets an autogenerated name in this case, which is "Param_0") and then created a new expression almost the same as the old one, but using the new parameter.

public static void Main()
{
    String thing = "test";

    Expression<Action<String>> expression = c => c.ToUpper();
    Delegate del = expression.Compile();
    del.DynamicInvoke(thing);

    Dictionary<String, Delegate> cache = new Dictionary<String, Delegate>();
    cache.Add(GenerateKey(expression), del);

    Expression<Action<String>> expression1 = d => d.ToUpper();
    var test = cache.ContainsKey(GenerateKey(expression1));
    Console.WriteLine(test);

}

public static string GenerateKey(Expression<Action<String>> expr)
{
    ParameterExpression newParam = Expression.Parameter(expr.Parameters[0].Type);
    Expression newExprBody = Expression.Call(newParam, ((MethodCallExpression)expr.Body).Method);
    Expression<Action<String>> newExpr = Expression.Lambda<Action<String>>(newExprBody, newParam);
    return newExpr.ToString();
}
Ito answered 20/1, 2010 at 20:35 Comment(2)
In the end I did fall back to something similar :) But I'm still looking for a more representative way to compare lambda-expressionsAdministrative
@Alexandra I'm trying to construct a new MethodCallExpression from an existing MethodCallExpression using the same body expression but with a different type argument. Could one accomplish this using something similar to your GenerateKey method? See: #14363887Tyrosine

© 2022 - 2024 — McMap. All rights reserved.