Переменная 'x.Sub' типа 'SubType' ссылается из области видимости '', но не определена как ошибка - PullRequest
3 голосов
/ 17 апреля 2019

Проверьте эту скрипку на наличие ошибки: https://dotnetfiddle.net/tlz4Qg

У меня есть два класса, подобных этому:

public class ParentType{
    private ParentType(){}

    public int Id { get; protected set; }
    public SubType Sub { get; protected set; }
}

public class SubType{
    private SubType(){}

    public int Id { get; protected set; }
}

Я собираюсь преобразовать многоуровневое анонимное выражение в многоуровневое неанонимноевыражение.Чтобы достичь этого, у меня есть выражение, подобное приведенному ниже:

x => new
{
   x.Id,
   Sub = new
   {
      x.Sub.Id
   }
}

Чтобы достичь этой цели, я преобразовал ее в выражение, подобное этому:

x => new ParentType()
{
   Id = x.Id,
   Sub = new SubType()
   {
      Id = x.Sub.Id
   },
 }

Но когда явызовите Compile() метод, я получаю следующую ошибку:

Переменная 'x.Sub' типа 'SubType' ссылается из области '', но она не определена

Вот мой класс посетителя:

public class ReturnTypeVisitor<TIn, TOut> : ExpressionVisitor
{
    private readonly Type funcToReplace;
    private ParameterExpression currentParameter;
    private ParameterExpression defaultParameter;
    private Type currentType;

    public ReturnTypeVisitor() => funcToReplace = typeof(Func<,>).MakeGenericType(typeof(TIn), typeof(object));

    protected override Expression VisitNew(NewExpression node)
    {
        if (!node.Type.IsAnonymousType())
            return base.VisitNew(node);

        if (currentType == null)
            currentType = typeof(TOut);

        var ctor = currentType.GetPrivateConstructor();
        if (ctor == null)
            return base.VisitNew(node);

        NewExpression expr = Expression.New(ctor);
        IEnumerable<MemberBinding> bindings = node.Members.Select(x =>
        {
            var mi = currentType.GetProperty(x.Name);

 //if the type is anonymous then I need to transform its body
                if (((PropertyInfo)x).PropertyType.IsAnonymousType())
                {
 //This section is became unnecessary complex!
 //
                    var property = (PropertyInfo)x;

                    var parentType = currentType;
                    var parentParameter = currentParameter;

                    currentType = currentType.GetProperty(property.Name).PropertyType;

                    currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);

 //I pass the inner anonymous expression to VisitNew and make the non-anonymous expression from it
                    var xOriginal = VisitNew(node.Arguments.FirstOrDefault(a => a.Type == property.PropertyType) as NewExpression);

                    currentType = parentType;
                    currentParameter = parentParameter;

                    return (MemberBinding)Expression.Bind(mi, xOriginal);
                }
                else//if type is not anonymous then simple find the property and make the memberbinding
                {
                    var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
                    return (MemberBinding)Expression.Bind(mi, xOriginal);
                }
        });

        return Expression.MemberInit(expr, bindings);
    }

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

        defaultParameter = node.Parameters.First();

        currentParameter = defaultParameter;
        var body = Visit(node.Body);

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

И используйте его так:

public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
    {
        var visitor = new ReturnTypeVisitor<Tin, Tout>();
        var result = (Expression<Func<Tin, Tout>>)visitor.Visit(source);
        return result;// result.Compile() throw the aforementioned error
    }

Вот методы расширения, используемые в моем классе посетителя:

public static ConstructorInfo GetPrivateConstructor(this Type type) =>
            type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);

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

 return markedWithAttribute
               && (typeName.StartsWith("<>") || type.Name.StartsWith("VB$"))
               && typeName.Contains("AnonymousType");
}

Обновление

Вот ссылка .Net Fiddle для проблемы: https://dotnetfiddle.net/tlz4Qg

Обновление

Я удалилдополнительные коды, которые, кажется, выходят за рамки проблемы.

1 Ответ

4 голосов
/ 21 апреля 2019

Причиной рассматриваемой проблемы является строка

currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);

внутри VisitNew метод.

В вашем примере создается новый параметр с именем "x.Sub", поэтому, если мы пометим параметры как {}, фактический результат будет

Sub = new SubType()
{
    Id = {x.Sub}.Id
}, 

скорее, чем ожидалось

Sub = new SubType()
{
    Id = {x}.Sub.Id
},

Как правило, вы не должны создавать новые ParameterExpression с, за исключением случаев переопределения лямбда-выражений. И все вновь созданные параметры должны быть переданы в вызов Expression.Lambda, в противном случае они будут считаться «не определенными».

Также обратите внимание, что в коде посетителя есть некоторые допущения, которые в целом не выполняются. Например

var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);

не будет работать внутри вложенного new, потому что там вам нужен доступ к члену параметра x, например x.Sub.Id, а не x.Id. Что в основном является выражением corersonding из NewExpression.Arguments.

Обработка вложенных лямбда-выражений или членов типа коллекции и методов LINQ с посетителями выражений требует гораздо большего контроля состояния. При преобразовании простого вложенного анонимного выражения new, как в примере, даже не требуется ExpressionVisitor, поскольку его можно легко достичь с помощью простого рекурсивного метода, такого как:

public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
    return Expression.Lambda<Func<Tin, Tout>>(
        Transform(source.Body, typeof(Tout)),
        source.Parameters);
}

static Expression Transform(Expression source, Type type)
{
    if (source.Type != type && source is NewExpression newExpr && newExpr.Members.Count > 0)
    {
        return Expression.MemberInit(Expression.New(type), newExpr.Members
            .Select(m => type.GetProperty(m.Name))
            .Zip(newExpr.Arguments, (m, e) => Expression.Bind(m, Transform(e, m.PropertyType))));
    }
    return source;
}
...