Variable 'x.Sub' of type 'SubType' referenced from scope '' but it is not defined error

和自甴很熟 提交于 2019-12-01 07:39:46

问题


Check this fiddle for the error: https://dotnetfiddle.net/tlz4Qg

I have two classes like this:

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

I am going to transform a multilevel anonymous expression to a multilevel non-anonymous expression. To achieve this I have an expression like the below-mentioned one:

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

To achieve that goal, I have transformed it to an expression like this:

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

But when I call Compile() method, I get the following error:

Variable 'x.Sub' of type 'SubType' referenced from scope '' but it is not defined

Here is my visitor class:

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

And use it like this:

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
    }

Here is the extension methods used inside my Visitor class:

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

Update

Here is the .Net Fiddle link for the problem: https://dotnetfiddle.net/tlz4Qg

Update

I have removed the extra codes that seems to be out of the problem scope.


回答1:


The cause of the problem in question is the line

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

inside VisitNew method.

With your sample, it creates a new parameter called "x.Sub", so if we mark the parameters with {}, the actual result is

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

rather than expected

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

In general you should not create new ParameterExpressions except when remapping lambda expressions. And all newly created parameters should be passed to Expression.Lambda call, otherwise they will be considered "not defined".

Also please note that the visitor code has some assumptions which doesn't hold in general. For instance

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

won't work inside nested new, because there you need access to a member of the x parameter like x.Sub.Id rather than x.Id. Which is basically the corersonding expression from NewExpression.Arguments.

Processing nested lambda expressions or collection type members and LINQ methods with expression visitors requires much more state control. While converting simple nested anonymous new expression like in the sample does not even need a ExpressionVisitor, because it could easily be achieved with simple recursive method like this:

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


来源:https://stackoverflow.com/questions/55730825/variable-x-sub-of-type-subtype-referenced-from-scope-but-it-is-not-define

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!