一个有趣的c#NullReferenceException

时间:2016-05-27 21:16:42

标签: c#

我在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时起作用。

  

所以我的问题是为什么它适用于引用类型而不是值类型?

2 个答案:

答案 0 :(得分:1)

(expression.Body as MemberExpression).Member

如果expression.Body无法转换为MemberExpression,则表达式返回null。当然,尝试访问Member成员会引发NullReferenceException

答案 1 :(得分:1)

第二次调用很容易看到expression.Body as MemberExpressionnull,因为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,基本上是进行装箱。如果您尝试创建相同的表达式但没有转换,则会抛出异常。