Kieron
Kieron

Reputation: 27127

Rewrite the type of an Expression<T>

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

Answers (2)

Cheng Chen
Cheng Chen

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

NotFound
NotFound

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

Related Questions