我在Web应用程序中遇到了NullReferenceException,并花了很多时间来实际找到问题。我用控制台应用程序重现了这个问题。您可以尝试按原样运行以下代码 -
using System;
using System.Linq.Expressions;
namespace Expression
{
public struct ValueType { }
public class ReferenceType { }
public class MyClass
{
public ReferenceType ReferenceType { get; set; }
public ValueType ValueType { get; set; }
}
class Program
{
public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
{
return (expression.Body as MemberExpression).Member.Name;
}
static void Main(string[] args)
{
MyClass c1 = new MyClass();
MyClass c2 = new MyClass();
Console.WriteLine(GetPropertyName<MyClass>(x => x.ReferenceType));
// No Error
Console.WriteLine(GetPropertyName<MyClass>(x => x.ValueType));
// System.NullReferenceException
}
}
}
所以问题是泛型函数GetPropertyName在作为具有Reference Type属性但是Value Type属性的函数的参数给出的表达式在System.NullReferenceException
处导致(expression.Body as MemberExpression).Member
时起作用。
所以我的问题是为什么它适用于引用类型而不是值类型?
答案 0 :(得分:1)
(expression.Body as MemberExpression).Member
如果expression.Body
无法转换为MemberExpression
,则表达式返回null
。当然,尝试访问Member
成员会引发NullReferenceException
。
答案 1 :(得分:1)
第二次调用很容易看到expression.Body as MemberExpression
为null
,因为expression.Body
的类型为UnaryExpression
,而不是MemberExpression
。
为什么呢?实际操作是“转换为System.Object
”。将值类型转换为引用称为装箱,如果委托必须返回object
,则需要装箱。
我们可以在下面的CIL上说明它,它可以用lambdas表示:
//x => x.ReferenceType
ldarg.0
callvirt instance class ReferenceType MyClass::get_ReferenceType()
ret
//x => x.ValueType
ldarg.0
callvirt instance class ReferenceType MyClass::get_ReferenceType()
box
ret
如您所见,第二个函数包含一个额外的box
指令,正如我上面所描述的那样。生成的表达式类似于“转换”操作中的CIL,基本上是进行装箱。如果您尝试创建相同的表达式但没有转换,则会抛出异常。