用表达式树解析 XML
Parse XML with 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);
}
运行没有问题。我尝试使用表达式树创建解析器委托,但因空 class 或空引用异常而失败。
这是它的样子
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);
}
在调试视图中,一切似乎都很好,但在运行过程中,它会变成碎石。代码有什么问题?
我看到这里有两个问题:
- 你的变量
authorParameter
应该是一个常量。
- 您的参数表达式列表应该只包含变量
clazz
.
那两条更正后的行看起来像这样:
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)中声明的,因此不应在此处列出。
我有像这样的示例代码
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);
}
运行没有问题。我尝试使用表达式树创建解析器委托,但因空 class 或空引用异常而失败。 这是它的样子
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);
}
在调试视图中,一切似乎都很好,但在运行过程中,它会变成碎石。代码有什么问题?
我看到这里有两个问题:
- 你的变量
authorParameter
应该是一个常量。 - 您的参数表达式列表应该只包含变量
clazz
.
那两条更正后的行看起来像这样:
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)中声明的,因此不应在此处列出。