从范围''引用了类型为'SubType'的变量'x.Sub',但未定义错误 [英] Variable 'x.Sub' of type 'SubType' referenced from scope '' but it is not defined error

查看:95
本文介绍了从范围''引用了类型为'SubType'的变量'x.Sub',但未定义错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

检查此提琴是否存在错误: https://dotnetfiddle.net/tlz4Qg

Check this fiddle for the error: 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; }
}

我将把一个多级匿名表达式转换成一个多级非匿名表达式.为此,我有一个类似下面提到的表达式:

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

但是当我调用Compile()方法时,出现以下错误:

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

从作用域''引用的类型为'SubType'的变量'x.Sub',但未定义

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

这是我的访客班:

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
    }

这是我的Visitor类中使用的扩展方法:

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

更新

以下是该问题的.Net小提琴链接: https://dotnetfiddle.net/tlz4Qg

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

更新

我删除了似乎不在问题范围内的多余代码.

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

推荐答案

有问题的原因是线路

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

内部VisitNew方法.

在您的示例中,它将创建一个名为"x.Sub"的新参数,因此,如果我们用{}标记这些参数,则实际结果为

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

而不是预期的

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

通常,除非重新映射lambda表达式,否则您不应创建新的ParameterExpression.并且所有新创建的参数都应传递给Expression.Lambda调用,否则它们将被视为未定义".

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

在嵌套的new内部不起作用,因为您需要访问x参数的成员,例如x.Sub.Id而不是x.Id.基本上是NewExpression.Arguments中的对应表达式.

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.

使用表达式访问者处理嵌套的lambda表达式或集合类型成员和LINQ方法需要更多的状态控制.像示例中那样转换简单的嵌套匿名new表达式时甚至不需要ExpressionVisitor,因为它可以通过简单的递归方法轻松实现,如下所示:

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

这篇关于从范围''引用了类型为'SubType'的变量'x.Sub',但未定义错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆