可空类型是很好的东西,泛型也很不错。
但是当我们需要访问动态创建的可空类型,问题就来了。
看下面的代码
PropertyInfo propHasValue = typeof(int?).GetProperty("HasValue");
int? example = null;
bool hasValue = (bool)propHasValue.GetValue(example, null);
Console.WriteLine(hasValue);
int? example = null;
bool hasValue = (bool)propHasValue.GetValue(example, null);
Console.WriteLine(hasValue);
按照我们的预期结果,输出应为 false;
但是,CLR 并未正常运行,而是抛出了 System.Reflection.TargetException,非静态方法需要一个目标。
咋一看,让人丈二和尚摸不着头脑,啥意思啊?
具体细节在 System.Nullable<T> 的 Equals 实现细节上。
这里附上 Reflector 取出的代码
public override bool Equals(object other)
{
if (!this.HasValue)
{
return (other == null);
}
if (other == null)
{
return false;
}
return this.value.Equals(other);
}
{
if (!this.HasValue)
{
return (other == null);
}
if (other == null)
{
return false;
}
return this.value.Equals(other);
}
意思是,如果可空类型实例的值为空,那么和 null 比较相等时,总是返回 true。
这是可空类型对比较语法的支持,在这里完成了对语言级别的 null 判断支持。
但是这样就对反射 API 造成了障碍,因为反射是直接调用对象复写后的 Equals 的,那么在反射中的后期绑定调用,将总是认为值为空的可空类型的实例为空(这句话真别扭)。
那么在动态调用中,如果调用的成员为 Nullable<T> 的非静态成员,并且 HasValue 为 false,总是会抛出 TargetException 异常。
这个问题是相对的,从可空类型存在的意义上来看,重载 null 比较并没有错,这是 C# 中支持可空类型的语法的基础, 在大多数场景,这是没有问题的。
但是在某些特殊的场景中,譬如动态取 Nullable<T> 的非静态成员,将毫无办法。
至少,应该提供一个 object.RawEquals(object,object),该方法不受运算符重载影响。
这应该算是一个 BUG,期待 CLR 在以后的版本中修正这个问题。