32

我正在调查一位同事在通过 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中都是相同的类型。

该类型是一种称为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。

该类型既不会覆盖==运算符也不会覆盖运算!=符。

Equals(Identity other)即便如此,如果异常是从我的类的实现中抛出的,我希望在堆栈跟踪顶部看到我的Identity类,但它说NullReferenceException来自mscorlib.

我在 .NET Framework 版本 4.0.30319.269 上运行。

我没有内存转储,我以前没有见过这个,从那以后也没有复制过。尽管如此,我还是有义务进行调查并绝对确定它不是由我们的代码引起的,并且不会在生产中发生。

所以,真正的问题是:是什么导致了这个异常?

  • mscorlib 中的错误(似乎极不可能)
  • 机器上的瞬时内存损坏(可能,难以用证据备份)
  • 其他?

*响应Jordão的更新*

是否可以使用不是身份的对象调用该方法?

ConcurrentDictionary<TKey, TValue>类型是TKey=Identity并且没有任何子类Identity。所以,我看不出这怎么可能。

是否可以使用 null 调用该方法?

单元测试涵盖了Equals使用 null 调用所有实现的场景。

堆栈跟踪来自哪个版本的代码?也许一些旧版本容易受到异常影响?

我正在分析生成异常的相同代码。我检查了我同事计算机上运行的 .NET Framework 的版本也是 4.0.30319.269。

任何多线程场景都可能导致异常?这些通常很难重现,但可能值得研究。

是的,代码是多线程的,并且是有意的。所以,这就是我使用ConcurrentDictionary.

* 与 Jalal Aldeen Saa'd 的回应相关的后续行动 *

我原以为只有在使用“ref”关键字通过引用传递参数时,才会导致其他一些线程设置x为的竞争条件。我开始使用以下代码验证该理论:nullx

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;
}

并且测试完成没有错误。

如果我将签名更改为通过xy通过引用(即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)`),我可以强制执行该行为。

4

2 回答 2

4

I'll lay out my hypothesis here.

The stack is leading you to believe that this is where the crash occurs, but it occurs elsewhere. We're looking at the wrong thread.

I don't know if this would be practical, but sometimes good old "printf debugging" helps. What if you print out the value you're looking for before calling TryGetValue? You would see whether you strike a null or not.

于 2012-10-25T11:49:00.070 回答
1

大约几年前,我在 Equals 中遇到了一个空引用异常(不确定它是在 3.5 还是 4.0 中,或者它是否已修复)。我不清楚在您的情况下正在比较哪些类型,但在我的情况下,每当将泛型方法声明的 MethodInfo 反射对象与任何非 MethodInfo 对象进行比较时,都会发生这种情况...... Ka-boom!因此,如果您要比较反射对象,可能就是这样。如果你不是,那么至少我可以证明在 BCL 中至少有一个 Equals 实现可以在某些情况下无缘无故地抛出空引用异常,所以很可能还有其他的。即使是神圣的 .NET BCL 仍然是软件,所有软件都有错误。

于 2012-10-31T00:10:03.600 回答