gpt4 book ai didi

c# - 从范围 'x.Sub' 引用类型为 'SubType' 的变量 '' 但未定义错误

转载 作者:行者123 更新时间:2023-11-30 15:14:10 26 4
gpt4 key购买 nike

检查这个 fiddle 的错误: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() 方法时,出现以下错误:

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 类中使用的扩展方法:

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

更新

我已经删除了似乎超出问题范围的额外代码。

最佳答案

问题的原因是线路

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

一般情况下,除非重新映射 lambda 表达式,否则不应创建新的 ParameterExpression。并且所有新创建的参数都应传递给 Expression.Lambda 调用,否则它们将被视为“未定义”。

另请注意,访问者代码有一些一般情况下不成立的假设。例如

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

不会在嵌套的 new 中工作,因为在那里您需要访问 x 参数的成员,例如 x.Sub.Id而不是 x.Id。这基本上是来自 NewExpression.Arguments 的对应表达式。

使用表达式访问者处理嵌套的 lambda 表达式或集合类型成员和 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;
}

关于c# - 从范围 'x.Sub' 引用类型为 'SubType' 的变量 '' 但未定义错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55730825/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com