Подтвердить что ты не робот

Невозможное исключение NullReferenceException?

Я изучаю исключение, которое только что получил коллега во время запуска приложения через 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), и я не вижу никакой возможной причины для a 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. Таким образом, он с радостью примет значение null для других /obj и вернет false, как ожидалось.

Тип не переопределяет операторы == и !=.

Тем не менее, я ожидал бы увидеть мой класс поверх трассировки стека, если исключение было выбрано из реализации Equals(Identity other) в моем классе Identity, но оно говорит, что NullReferenceException происходит от mscorlib.

Я запускаю .NET Framework версии 4.0.30319.269.

У меня нет дампа памяти, и я не видел этого раньше и не воспроизводил его с тех пор. Тем не менее, я обязан исследовать и быть абсолютно уверенным, что это не вызвано нашим кодом и что это не произойдет в производстве.

Итак, реальный вопрос: что вызвало это исключение?

  • Ошибка в mscorlib (кажется маловероятным)
  • Повреждение повреждающей памяти на машине (возможно, трудно выполнить резервное копирование с доказательством)
  • Другое?

* Обновления в ответ на Jordão *

Можно ли вызвать метод с объектом, который не является идентификатором?

Напечатано ConcurrentDictionary<TKey, TValue> такое, что TKey= Identity и ничего подклассы Identity. Итак, я не вижу, как это возможно.

Можно ли вызвать метод с нулевым значением?

Модульные тесты охватывают сценарий вызова всех реализаций Equals с нулевым значением.

Какая версия кода - это трассировка стека? Может быть, какая-то более старая версия восприимчива к исключению?

Я анализирую тот же код, который генерировал исключение. Я проверил, что версия .NET Framework, работающая на компьютере моих коллег, также 4.0.30319.269.

Любой многопоточный сценарий может вызвать исключение? Обычно их трудно воспроизвести, но их стоит исследовать.

Да, код многопоточный и должен быть. Поэтому я использую ConcurrentDictionary.

* Последующая реакция, связанная с ответом Джалала Алдина Саа'да *

Я бы подумал, что условие гонки, когда некоторые другие наборы потоков x to null могут быть причиной, если параметр x был передан по ссылке с использованием ключевого слова 'ref'. Я решил проверить эту теорию следующим кодом:

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 a NullReferenceException , but this does not match the method signature of GenericEqualityComparer.Equals(T x, T y) `.

4b9b3361

Ответ 1

Я изложу свою гипотезу здесь.

Стек позволяет вам поверить, что именно здесь происходит сбой, но он встречается в другом месте. Мы смотрим на неправильную тему.

Я не знаю, будет ли это практично, но иногда помогает старая "отладка" printf. Что делать, если вы распечатаете значение, которое вы ищете, прежде чем называть TryGetValue? Вы увидите, удаляете ли вы нуль или нет.

Ответ 2

Я столкнулся с исключительным исключением в Equals около пары лет назад (не уверен, что он был в 3.5 или 4.0, или если он когда-либо был исправлен). Мне непонятно, какие типы сравниваются в вашем случае, но в моей ситуации это происходило при сравнении объекта отражения MethodInfo для объявления универсального метода для ЛЮБОГО объекта non-MethodInfo... Ka-boom! Итак, если вы сравниваете объекты отражения, это может быть и так. Если вы этого не сделаете, то, по крайней мере, я могу засвидетельствовать тот факт, что в BCL существует хотя бы одна реализация Equals, которая в определенных ситуациях может исключать ненужные ссылочные исключения, поэтому вполне могут быть другие. Даже священный .NET BCL по-прежнему является программным обеспечением, и у ВСЕ ПО есть ошибки.