Я изучаю исключение, которое только что получил коллега во время запуска приложения через 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) `.