Search code examples
c#lambdaexpression-trees

Parse XML with Expression trees


I Have sample code like

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

Which runs without problem. I tried to create parser delegate with expression tree and failed with empty class or null reference exception. Here is how it looks

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

and usage is

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

With Debug view everything seems fine but on the fly, it grounds to gravel. Whats wrong with code?


Solution

  • There are two problems here that I can see:

    1. Your variable authorParameter should probably be a constant.
    2. Your parameter expression list should only include variable clazz.

    Those two corrected lines look like this:

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

    The thing really triggering the NullReferenceException was the second problem: You declared element to be a variable inside the block, which effectively hides the parameter element declared in the outside scope. That's somewhat equivalent to doing the following (though the C# compiler will reject this):

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

    The only variables that should be listed in that variables parameter of Expression.Block are variables that are declared in the block, and not in any other scope. As element was declared in an outer scope (the lambda) it shouldn't be listed there.