Search code examples
c#linqexpressionlinq-expressions

Extract a strongly typed expression from a function expression body


I'm trying to write a "relaxed" comparison method which operates on Expressions and is used in Entity Framework. It consists of a chain of Replace() calls and ToLower() to perform an inexact match on compound names.

E.g., "OConnor" will match "o'connor" in this relaxed search, and "jean luc" would match "Jean-Luc". etc.

I have written a helper method which chains a Replace() call for each value passed in to strip out the value from the input string. This method works but I can't work out how to maintain strong typing when using it with both a property access expression and a constant string.

Here, is a strongly-typed variant accepting functions expressions which return a string:

private Expression ExpressionStrip<T>(Expression<Func<T, string>> exprStr, params string[] saValues)
{
    if (saValues.Length == 0)
    {
        return exprStr.Body;
    }

    var exprEmptyString = Expression.Constant("");
    var exprResult = Expression.Call(exprStr.Body, "Replace", null, Expression.Constant(saValues[0]), exprEmptyString);

    for (var i = 1; i < saValues.Length; i++)
    {
        exprResult = Expression.Call(exprResult, "Replace", null, Expression.Constant(saValues[i]), exprEmptyString);
    }

    return exprResult;
}

Example use: var stripped = ExpressionStrip(p => p.FullName.LastName, " ", "-", "'", "’");

But I also need to perform the same action on the constant to compare against:

var stripped2 = ExpressionStrip("Jean-Luc", " ", "-", "'", "’");

I have done this using the base Expression type:

private Expression ExpressionStrip(Expression str, params string[] saValues)
{
    if (saValues.Length == 0)
    {
        return str;
    }

    var exprEmptyString = Expression.Constant("");
    var exprResult = Expression.Call(str, "Replace", null, Expression.Constant(saValues[0]), exprEmptyString);

    for (var i = 1; i < saValues.Length; i++)
    {
        exprResult = Expression.Call(exprResult, "Replace", null, Expression.Constant(saValues[i]), exprEmptyString);
    }

    return exprResult;
}       

I can use it by passing in the expression body of my lambda expression:

// Where exprStr is of type Expression<Func<T, string>>
ExpressionStrip(exprStr.Body, " ", "-", "'", "’");

And also by passing the constant:

ExpressionStrip(Expression.Constant("Jean-Luc"), " ", "-", "'", "’");

So, it works, but I've lost the strong typing for an Expression which resolves to type String, so this kind of thing compiles: ExpressionStrip(Expression.Constant(true), " "), and I don't want it to.

I tried this signature: Expression ExpressionStrip(Expression<string> str, params string[] saValues) but LambdaExpression.Body returns Expression which cannot be converted to Expression<string>, so I can't use it with my exprStr.Body.

Is it possible to cast/convert/extract in some way a strongly typed function body from the lambda function expression?


Solution

  • By design, expression trees are all weakly-typed, except for lambda expressions. Type checking is mostly all done at runtime. Expression<T> represents a lambda expression whose type is the delegate type T. It does not mean "an expression of type T" in general. There is no type that represents that.

    The closest you can get is probably to take a Expression<Func<string>>.

    private Expression ExpressionStrip(Expression<Func<string>> exprStr, params string[] saValues) { ... }
    

    Then, to pass a Expression<Func<T, string>> to this, you do:

    // suppose exprStr is of type Expression<Func<T, string>>
    ExpressionStrip(Expression.Lambda<Func<string>>(exprStr.Body), "x", "y", "z");
    

    One could argue that this is still not type safe because you can replace exprStr.Body with anything.

    // this compiles and throws an exception at runtime
    ExpressionStrip(Expression.Lambda<Func<string>>(Expression.Constnat(1)), "x", "y", "z");
    

    Again, this is because expression trees are weakly typed by design.