【发布时间】:2012-10-15 03:13:36
【问题描述】:
我正在调查一位同事在通过 Visual Studio 2010 运行应用程序时遇到的异常:
System.NullReferenceException was unhandled by user code
Message=Object reference not set to an instance of an object.
Source=mscorlib
StackTrace:
at System.Collections.Generic.GenericEqualityComparer`1.Equals(T x, T y)
at System.Collections.Concurrent.ConcurrentDictionary`2.TryGetValue(TKey key, TValue& value)
at xxxxxxx.xxxxxxx.xxxxxxx.RepositoryBase`2.GetFromCache(TIdentity id)
使用.NET Reflector,我查看了GenericEqualityComparer<T>.Equals(T x, T y) 的代码,我看不出NullReferenceException 的任何可能原因。
//GenericEqualityComparer<T>.Equals(T x, T y) from mscorlib 4.0.30319.269
public override bool Equals(T x, T y)
{
if (x != null)
{
return ((y != null) && x.Equals(y));
}
if (y != null)
{
return false;
}
return true;
}
T,TKey和TIdentity的类型在这个stack trace中都是同一个类型。
该类型是一个名为Identity 的自定义类型,它实现了IEquatable<Identity>。它是不可变的,并且不能用它在其Equals(Identity other) 的实现中使用的字段的空值来构造。它还像这样覆盖Equals(object obj):
public override bool Equals(object obj)
{
if ((object)this == obj)
{
return true;
}
return Equals(obj as Identity);
}
public bool Equals(Identity other)
{
if ((object)this == (object)other)
{
return true;
}
if ((object)other == null)
{
return false;
}
if (!FieldA.Equals(other.FieldA))
{
return false;
}
return FieldB.Equals(other.FieldB);
}
围绕Equals 实现,我有一套相当详尽的单元测试。因此,它会很乐意接受 other/obj 的 null 值,并按预期返回 false。
该类型既不会覆盖== 运算符,也不会覆盖!= 运算符。
即便如此,如果在我的 Identity 类中从 Equals(Identity other) 的实现中抛出异常,我希望在堆栈跟踪顶部看到我的类,但它说 NullReferenceException 来自 @ 987654341@.
我在 .NET Framework 版本 4.0.30319.269 上运行。
我没有内存转储,而且我以前从未见过它,此后也没有复制过它。尽管如此,我还是有义务进行调查并绝对确定它不是由我们的代码引起的,并且不会在生产中发生。
那么,真正的问题是:是什么导致了这个异常?
- mscorlib 中的错误(似乎极不可能)
- 机器上的瞬时内存损坏(可能,难以用证据备份)
- 其他?
* 响应 Jordão 的更新 *
是否可以使用不是身份的对象调用方法?
ConcurrentDictionary<TKey, TValue> 是这样输入的,TKey = Identity 并且没有任何子类 Identity。所以,我看不出这怎么可能。
可以用null调用方法吗?
单元测试涵盖了使用 null 调用所有 Equals 实现的场景。
堆栈跟踪来自哪个版本的代码?也许一些旧版本容易受到异常影响?
我正在分析生成异常的相同代码。我检查了我同事计算机上运行的 .NET Framework 的版本也是 4.0.30319.269。
任何多线程场景都可能导致异常?这些通常很难重现,但可能值得研究。
是的,代码是多线程的,并且是有意的。所以,这就是我使用ConcurrentDictionary 的原因。
* 与 Jalal Aldeen Saa'd 的回应相关的后续行动 *
我原以为只有当参数x 使用“ref”关键字通过引用传递时,其他线程将x 设置为null 的竞争条件才是原因。我开始使用以下代码验证该理论:
ManualResetEvent TestForNull = new ManualResetEvent(false);
ManualResetEvent SetToNull = new ManualResetEvent(false);
[TestMethod]
public void Test()
{
var x = new object();
var y = new object();
var t = Task.Factory.StartNew(() =>
{
return Equals(x, y);
});
TestForNull.WaitOne(); //wait until x has been tested for null value
x = null;
SetToNull.Set(); //signal that x has now been set to null
var result = t.Result;
Assert.IsFalse(result);
}
public bool Equals<T>(T x, T y)
{
if (x != null)
{
TestForNull.Set(); //signal that we have determined that x was not null
SetToNull.WaitOne(); //wait for original x value to be set to null
//would fail here if setting the outer scope x to null affected
//the value of x in this scope
return ((y != null) && x.Equals(y));
}
if (y != null)
{
return false;
}
return true;
}
并且测试完成且没有错误。
如果我将签名更改为通过引用传递x 和y(即public bool Equals<T>(ref T x, ref T y) then the test fails with aNullReferenceException, but this does not match the method signature ofGenericEqualityComparer.Equals(T x, T y)`,我可以强制执行该行为。
【问题讨论】:
-
在覆盖的
Equals实现中,对obj执行null 检查(并返回false)并查看是否仍然抛出错误。 -
问题是这个异常只被观察过一次,我不能轻易重现它,所以我只能通过对代码的静态分析来诊断原因。
-
既然你说它是多线程的,那么在 if 检查之后但在等号之前,是否可能 x 在其他地方被设置为 null。一个简单的检查方法是在 Equals 覆盖中添加一个 sleep 并在另一个线程中将 x 值设置为 null。
-
您是否有在多线程环境中执行 Equals 方法的单元测试?如果没有,我会添加一些。
-
没有任何单元测试来以多线程方式显式测试 Equals,但是对象是不可变的,并且只比较在构造函数中设置的私有字段并且不能为空,否则构造函数会失败。此外,错误似乎不是来自我的 Equals 方法,而是来自 GenericEqualityComparer。
标签: c# nullreferenceexception mscorlib