Выражение выражения> к выражению> - PullRequest
0 голосов
/ 13 апреля 2019

У меня есть объект Expression<Func<Tin, object>>, и мне нужно привести его к объекту Expression<Func<Tin, Tout>>.

На самом деле у меня есть это:

x => new <>f__AnonymousType6`1(MyProp = x.MyProp)

, и мне нужно иметь егоas:

x => new MyType(){MyProp = x.MyProp}

Обратите внимание, что у меня есть AnonymousType здесь!

Для этого я написал следующую функцию:

public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
    var param = Expression.Parameter(typeof(Tout));

    var body = new Visitor<Tout>(param).Visit(source.Body);

    Expression<Func<Tin, Tout>> lambda = Expression.Lambda<Func<Tin, Tout>>(body, param);
    return lambda;
}

И класс Visitor:

class Visitor<T> : ExpressionVisitor
{
    ParameterExpression _parameter;

    public Visitor(ParameterExpression parameter)=>_parameter = parameter;

    protected override Expression VisitParameter(ParameterExpression node)=>_parameter;

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
            throw new NotImplementedException();

        var memberName = node.Member.Name;

        var otherMember = typeof(T).GetProperty(memberName);

        var inner = Visit(node.Expression);
        return Expression.Property(inner, otherMember);
    }
}

Но когда я запускаю его, я получаю эту ошибку:

System.ArgumentException: 'Выражение типа' <> f__AnonymousType6`1 [System.String] 'не может использоваться для типа возвращаемого значения 'MyType' '

Обновление

В классах Tin и Tout У меня есть несколько параметрических конструкторов и закрытый параметрбез конструктора.Я не хочу использовать параметрические конструкторы, поскольку они имеют аргументы, которые могут отличаться от требуемого выражения.Мне нужно построить выражение, используя закрытый конструктор без параметров.

Так что, если я использую следующий код:

var ctor = typeof(TOut).GetPrivateConstructor();
if (ctor != null) // can replace
     return Expression.New(ctor, node.Arguments);

Или даже это:

var ctor = typeof(TOut).GetPrivateConstructor();
    if (ctor != null) // can replace
    {
         var expr = Expression.New(ctor);
         expr.Update(node.Arguments);//<=====Exception in this line
         return expr;
    }

Iполучит следующую ошибку:

Неверное количество аргументов для конструктора

И если я воспользуюсь следующим:

var ctor = typeof(TOut).GetPrivateConstructor();
if (ctor != null) // can replace
     return Expression.New(ctor);

Я пропущуАргументы!

Обновление 2

Если я использую его как:

var ctor = typeof(TOut).GetPrivateConstructor();
if (ctor != null) // can replace
{
   var expr = Expression.New(ctor);

   FieldInfo argementsField = expr.GetType().GetRuntimeFields().FirstOrDefault(a => a.Name == "_arguments");
   argementsField.SetValue(expr, node.Arguments);

   expr.Update(node.Arguments);
   return expr;
}

Выражение будет построено, но не будет выполнено как оновыдает следующее:

x => new MyType(MyProp = x.MyProp)

Повторное неверное отображение приведет к следующей ошибке, как и ожидалось:

Неверное количество аргументов для конструктора

1 Ответ

2 голосов
/ 15 апреля 2019

Предполагая, что MyType выглядит следующим образом

public class MyType
{
    public MyType(string myProp)
    {
        MyProp = myProp;
    }

    public string MyProp { get; set; }
}

, вы можете создать общего посетителя:

public class MyVisitor<TIn, TOut> : ExpressionVisitor
{
    private readonly Type funcToReplace;

    public MyVisitor()
    {
        funcToReplace = typeof(Func<,>).MakeGenericType(typeof(TIn), typeof(object));
    }

    // this hack taken from https://stackoverflow.com/a/2483054/4685428
    // and https://stackoverflow.com/a/1650895/4685428
    private static bool IsAnonymousType(Type type)
    {
        var markedWithAttribute = type.GetCustomAttributes(
          typeof(CompilerGeneratedAttribute)).Any();
        var typeName = type.Name;

        return markedWithAttribute
          && typeName.StartsWith("<>")
          && typeName.Contains("AnonymousType");
    }

    protected override Expression VisitNew(NewExpression node)
    {
        if (IsAnonymousType(node.Type))
        {
            var arguments = node.Arguments.Select(a => a.Type).ToArray();
            var ctor = typeof(TOut).GetConstructor(arguments);
            if (ctor != null) // can replace
                return Expression.New(ctor, node.Arguments);
        }
        return base.VisitNew(node);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        if (typeof(T) != funcToReplace)
            return base.VisitLambda(node);

        var p = node.Parameters.First();
        var body = Visit(node.Body);

        return Expression.Lambda<Func<TIn, TOut>>(body, p);
    }
}

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

Expression<Func<TypeOfX, object>> input = x => new {MyProp = x.MyProp};

var visitor = new MyVisitor<TypeOfX, MyType>();
var result = (Expression<Func<TypeOfX, MyType>>) visitor.Visit(input);

Некоторое объяснение:

В VisitNew мы проверяем, что конструктор принадлежит анонимному типу.Если это так, мы пытаемся найти в TOut тип конструктора с такими же аргументами.В случае успеха мы заменим конструктор анонимного типа конструктором из TOut

...