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?
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.