Перепишите тип выражения <T> - PullRequest
0 голосов
/ 01 февраля 2019

Можно ли динамически переписать Expression<T>, заменив элемент T другим типом?

Например, заменив DocumentTypeA на DocumentTypeB в следующих ситуациях:

  • 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"

Мне нужно будет сделатьрешение о том, какой тип использовать во время выполнения, а не во время компиляции.

Стоит также отметить, что DocumentTypeA и DocumentTypeB не связаны друг с другом, кроме того, что их свойства идентичны.

Конечным результатом будет их повторная обработка, чтобы они теперь выглядели как

  • 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"

Таким образом, фактическая часть выражения выражения остается неизменной, изменился только тип верхнего уровня.

Ответы [ 2 ]

0 голосов
/ 01 февраля 2019

Вы можете использовать ExpressionVisitor для замены типа.

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);
    }
}

В этом случае создайте экземпляр new ParameterRewriter<DocumentTypeA, DocumentTypeB>() и посетите исходное дерево выражений, вы получите то, что хотите.Метод расширения может быть более читабельным:

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);
    }
}

Использование простое:

Expression<Func<A, bool>> expA = x => x.Id == 1;
Expression<Func<B, bool>> expB = expA.RewriteParameter<A, B, bool>();
0 голосов
/ 01 февраля 2019

Используйте интерфейс, от которого оба класса наследуют и содержат свойства, которые идентичны в обоих классах, например

interface IDocumentTypes
{
    string AString { get; set; } //indicates that both classes need to implement this
    //etc...
}

class DocumentTypeA : IDocumentTypes
{
    //your class
}

Тогда оба ваших класса могут использовать Expression, когда он реализует интерфейс IDocumentTypes и все же быть строго набранным.Классы не должны иметь ничего общего, кроме реализации свойств / функций, определенных в интерфейсе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...