使用表达式树解析XML

时间:2015-12-01 14:21:44

标签: c# lambda expression-trees

我有像

这样的示例代码
class book
{
    public string author { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        XElement doc = XElement.Parse(@"<book><author>Gambardella, Matthew</author></book>");

        Func<XElement, book> parser = z =>
           {
               book b = new book();

               if (z.Element("author") != null)
               {
                   b.author = z.Element("author").Value;
               }

               return b;
           };


        var res= parser(doc);
    }

哪个运行没有问题。我尝试使用表达式树创建解析器委托,并且使用空类或空引用异常失败。 这是它的外观

public class Composer<T>
{
    public Func<XElement,T> Parser()
    {
        Type clazzType = typeof(T);
        Type elementType = typeof(XElement);

        var clazz = Expression.Parameter(clazzType, "clazz");
        var clazzInstance = Expression.Assign(clazz, Expression.New(clazzType));

        var element = Expression.Parameter(elementType, "xelement");

        var clazzProperty = clazzType.GetProperty("author");
        var expressionClazzProperty = Expression.PropertyOrField(clazz, clazzProperty.Name);
        var authorParameter = Expression.Parameter(typeof(XName), clazzProperty.Name);

        var elementCall = Expression.Call(element, elementType.GetMethod("Element"), authorParameter);
        var valueCall = Expression.Call(elementCall, elementType.GetProperty("Value").GetGetMethod());

        var ifExpression = Expression.IfThen(Expression.NotEqual(elementCall, Expression.Constant(null)),
            Expression.Assign(expressionClazzProperty, valueCall)
            );

        List<ParameterExpression> variables = new List<ParameterExpression> { clazz, element, authorParameter };
        List<Expression> body = new List<Expression> { clazzInstance, ifExpression,clazz };

        var block = Expression.Block(clazzType, variables, body);
        var finalExpression = Expression.Lambda<Func<XElement,T>>(block,element);
        return finalExpression.Compile();
    }
}

并且用法是

 static void Main(string[] args)
    {
        XElement doc = XElement.Parse(@"<book><author>Gambardella, Matthew</author></book>");

        Composer<book> composer = new Composer<book>();
        var parseDelegate= composer.Parser();

        var result = parseDelegate(doc);
    }

使用Debug视图,一切看起来都很好,但是在运行中,它会碾碎。代码有什么问题?

1 个答案:

答案 0 :(得分:2)

我可以看到两个问题:

  1. 您的变量authorParameter应该是常量。
  2. 您的参数表达式列表应仅包含变量clazz
  3. 这两条经过纠正的线条如下所示:

    var authorParameter = Expression.Constant((XName)clazzProperty.Name, typeof(XName));
    
    //...
    
    List<ParameterExpression> variables = new List<ParameterExpression> { clazz };
    

    真正触发NullReferenceException的事情是第二个问题:您声明element是块内的变量,它有效地隐藏了在外部范围内声明的参数element。这有点等同于执行以下操作(尽管C#编译器将拒绝此操作):

    Func<XElement, book> parser = z =>
    {
        XElement z;
        book b = new book();
        b.author = z.Element("author").Value;
        return b;
    };
    

    Expression.Block的变量参数中应列出的唯一变量是在块中声明的变量,而不是在任何其他范围中声明的变量。当element在外部范围(lambda)中声明时,它不应该列在那里。