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?
There are two problems here that I can see:
authorParameter
should probably be a constant.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.