我正在研究一位同事在通过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;
}
此stack trace中T,
TKey
和TIdentity
的类型都相同。
该类型是名为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
实现有一套相当详尽的单元测试。因此,它会愉快地接受其他/ obj的null值,并按预期返回false。
该类型不会覆盖==
运算符或!=
运算符。
即便如此,如果我的Equals(Identity other)
类Identity
的实现中抛出了异常,我希望看到我的类位于堆栈跟踪之上,但它会显示{{1来自NullReferenceException
。
我在.NET Framework 4.0.30319.269上运行。
我没有内存转储,之前我没有看过这个,之后就再也没有了。不过,我不得不进行调查,并且绝对肯定它不是由我们的代码引起的,并且它不会在生产中发生。
所以,真正的问题是:是什么导致了这个例外?
*响应Jordão*
的更新是否可以使用非Identity的对象调用该方法?
键入mscorlib
,ConcurrentDictionary<TKey, TValue>
= TKey
,没有子类Identity
。所以,我看不出它是如何可能的。
是否可以使用null调用方法?
单元测试涵盖了使用null调用所有Identity
实现的方案。
什么版本的代码是堆栈跟踪?也许某些旧版本容易受到例外的影响?
我正在分析生成异常的相同代码。我已经检查过在我的同事计算机上运行的.NET Framework版本也是4.0.30319.269。
任何多线程场景都可能导致异常?这些通常难以复制,但可能值得研究。
是的,代码是多线程的,并且打算这样做。所以,这就是我使用Equals
。
*与Jalal Aldeen Saa'd *的反应相关的跟进
我原本认为,如果使用'ref'关键字通过引用传递参数ConcurrentDictionary
,其他线程设置x
到null
的竞争条件只能是原因。我打算用以下代码验证该理论:
x
并且测试完成且没有错误。
如果我更改签名以通过引用传递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
NullReferenceException public bool Equals<T>(ref T x, ref T y) then the test fails with a
GenericEqualityComparer.Equals(T x, T y)`。
答案 0 :(得分:4)
我会在这里列出我的假设。
堆栈让你相信这是发生崩溃的地方,但它发生在其他地方。我们正在寻找错误的主题。
我不知道这是否实用,但有时旧的“printf调试”会有所帮助。如果您在致电TryGetValue
之前打印出您要查找的值,该怎么办?您会看到是否标记为空。
答案 1 :(得分:1)
几年前我在Equals中遇到了一个空引用异常(不确定它是在3.5还是4.0,或者它是否曾经修复过)。我不清楚在你的情况下比较了什么类型,但在我的情况下,只要比较一般方法声明的MethodInfo反射对象和任何非MethodInfo对象,就会发生这种情况...... Ka-boom!所以,如果你要比较反射对象,可能就是这样。如果你不是,至少我可以证明在BCL中至少有一个Equals实现可以在某些情况下无理由地抛出空引用异常,所以可能还有其他的。即使神圣的.NET BCL仍然是软件,所有软件都有漏洞。