Reputation: 27127
Is it possible to dynamically rewrite an Expression<T>
, replacing an element of T
with another type?
For example, replacing the DocumentTypeA
with DocumentTypeB
in the following situations:
Expression<Func<DocumentTypeA, bool>> expression = m => m.Details.Id == "an-id"
Expression<Func<DocumentTypeA, bool>> expression = m => m.Details.Id == "an-id" && m.AnInt == 42
Expression<Func<DocumentTypeA, bool>> expression = m => m.AString == "I'm a string"
I'll need to make the decision about what type to use at runtime, rather than compile time.
It's also worth noting that DocumentTypeA
and DocumentTypeB
don't relate to each other, apart from their properties are identical.
The end result would be to reprocess them so they now look like
Expression<Func<DocumentTypeB, bool>> expression = m => m.Details.Id == "an-id"
Expression<Func<DocumentTypeB, bool>> expression = m => m.Details.Id == "an-id" && m.AnInt == 42
Expression<Func<DocumentTypeB, bool>> expression = m => m.AString == "I'm a string"
So the actual comparison part of the expression remains unchanged, only the top-level type has changed.
Upvotes: 2
Views: 206
Reputation: 43531
You can use ExpressionVisitor to replace the type.
class ParameterRewriter<T, U> : ExpressionVisitor
{
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type.Equals(typeof(T)))
{
return Expression.Parameter(typeof(U), node.Name);
}
return base.VisitParameter(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is ParameterExpression paramExp && paramExp.Type.Equals(typeof(T)))
{
return Expression.MakeMemberAccess(
Expression.Parameter(typeof(U), paramExp.Name),
typeof(U).GetMember(node.Member.Name).Single());
}
return base.VisitMember(node);
}
protected override Expression VisitLambda<L>(Expression<L> node)
{
var parameters = node.Parameters.ToList();
var found = false;
for (var i = 0; i < parameters.Count; i++)
{
if (parameters[i].Type.Equals(typeof(T)))
{
parameters[i] = Expression.Parameter(typeof(U), parameters[i].Name);
found = true;
}
}
if (found)
{
return Expression.Lambda(node.Body, parameters);
}
return base.VisitLambda(node);
}
}
In this case, create an instance new ParameterRewriter<DocumentTypeA, DocumentTypeB>()
and visit the original expression tree, you will get what you want. An extension method maybe more readable:
public static class ExpressionExtensions
{
public static Expression<Func<U, R>> RewriteParameter<T, U, R>(this Expression<Func<T, R>> expression)
{
var rewriter = new ParameterRewriter<T, U>();
return (Expression<Func<U, R>>)rewriter.Visit(expression);
}
}
The usage is simple:
Expression<Func<A, bool>> expA = x => x.Id == 1;
Expression<Func<B, bool>> expB = expA.RewriteParameter<A, B, bool>();
Upvotes: 4
Reputation: 6227
Use an interface that both classes inherit from and contains the properties that are identical in both of the classes e.g.
interface IDocumentTypes
{
string AString { get; set; } //indicates that both classes need to implement this
//etc...
}
class DocumentTypeA : IDocumentTypes
{
//your class
}
Then both of your classes can use the Expression
when it implements the interface IDocumentTypes
and still be strongly typed. The classes don't need to have anything in common other than implementing the properties/functions defined in the interface.
Upvotes: 1