使用表达式树的动态方法:简单属性赋值失败

5
使用表达式树动态创建一个方法来执行简单的属性赋值代码:person.Name = "Joe Bloggs",却抛出了NullReferenceException异常——就像我创建的person参数没有被传递一样。
有什么想法吗?
class Person
{
    public string Name { get; set; }
}

static void ExpressionTest()
{
    var personParam = Expression.Parameter(typeof(Person), "person");

    var block = Expression.Block(new[] { personParam },
        Expression.Assign(
            Expression.Property(personParam, "Name"), Expression.Constant("Joe Bloggs"))
        );
    /* 
        * block.DebugView in debugger shows:
        * 
        *   .Block(MyProject.MyNamepace.Person $person) {
        *       $person.Name = "Joe Bloggs"
        *   }
        *   
        */

    var method = Expression.Lambda<Action<Person>>(block, personParam).Compile();

    var person = new Person();

    method(person); // **throws** System.NullReferenceException: ... at lambda_method(Closure , Person )

    Debug.WriteLine(person.Name); // I expect this to print "Joe Bloggs"
}

更新

感谢提供这些好的答案,我看到了@decPL的答案,他带领我从Expression.Block调用中删除了new[] { personParam }

现在我完全明白了,Block会定义变量作用域,而且你需要先定义它们(就像某些语言强制要求的那样)。我的问题是我不需要任何变量,但是被调试器展示的神奇的DebugView属性所误导。我的想法是它们是参数,而Block则像函数定义一样:

.Block(MyProject.MyNamepace.Person $person) {
    $person.Name = "Joe Bloggs"
}

...当然它不是。正如名称所示,它是一个代码

3个回答

8
为了澄清,BlockExpression 表示按顺序执行的表达式序列,并返回最后一个表达式的值。
你正在使用的 Expression.Block 版本 "创建包含给定变量和表达式的 BlockExpression"(http://msdn.microsoft.com/en-us/library/dd324074(v=vs.110).aspx),因此它创建了以下代码:
{
    Person person;
    person.Name = "Joe Bloggs";
}

所以它明显抛出了NullReferenceException。

太好了 - 遗憾的是 DebugView 没有像那样清晰地显示它。 - Duncan Smart

4

如果删除该块,则其按预期工作:

var personParam = Expression.Parameter(typeof(Person), "person");
var method = Expression.Lambda<Action<Person>>(Expression.Assign(
                    Expression.Property(personParam, "Name"), Expression.Constant("Joe Bloggs")), personParam).Compile();

为了修复你的原始代码,你应该删除传递给块的参数表达式:

var block = Expression.Block(
                Expression.Assign(
                    propertyExpr, Expression.Constant("Joe Bloggs"))
                );

谢谢 @Lee,我需要这个块是因为我将要(并最终)将其构建起来以设置多个属性。 - Duncan Smart

1
您正在将方法的参数作为变量传递给Expression.Block方法,但实际上它不是变量。如果您将其删除,则会正常工作:
var block = Expression.Block(
               Expression.Assign(
                   Expression.Property(personParam, "Name"),
                   Expression.Constant("Joe Bloggs")));

当然,如果这就是你的程序的全部内容,你可能可以放弃块表达式。

我明白了!我看到了 Block(IEnumerable<ParameterExpression> variables, IEnumerable<Expression> expressions) 的第一个参数 - 由于它是 ParameterExpression 类型,我想到了“啊,参数”。 - Duncan Smart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接