Je recherche une exception qu'un collègue viens de recevoir lors de l'exécution d'une application par le biais de 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)
À l'aide de .NET Réflecteur, j'ai regardé le code pour GenericEqualityComparer<T>.Equals(T x, T y)
, et je ne vois aucune cause possible pour un 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;
}
Le type d' T,
TKey
et TIdentity
sont tous du même type dans cette trace de la pile.
Le type est un type personnalisé appelé Identity
qui implémente IEquatable<Identity>
. Il est immuable et ne peut pas être construit avec des valeurs null dans les champs qu'il utilise dans sa mise en œuvre d' Equals(Identity other)
. Il remplace également Equals(object obj)
comme ceci:
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);
}
J'ai un assez exhaustive de tests unitaires autour de l' Equals
des implémentations. Donc, il se fera un plaisir d'accepter une valeur nulle pour les autres/obj et retournera false comme prévu.
Le type n'a pas supplanter l' ==
opérateurs ni !=
opérateurs.
Même ainsi, je m'attends à voir ma classe sur le dessus de la trace de la pile si l'exception a été levée à partir de la mise en œuvre de l' Equals(Identity other)
mon Identity
classe, mais il est dit que l' NullReferenceException
provient mscorlib
.
Je suis en cours d'exécution sur .NET Framework version 4.0.30319.269.
Je n'ai pas de vidage de la mémoire, et je n'ai pas vu ça avant, et n'ont pas reproduit depuis. Pourtant, je suis obligé d'enquêter et d'être absolument certain qu'il n'est pas provoqué par notre code et qu'il n'arrivera pas dans la production.
Donc, la vraie question est: Quelle est la cause de cette exception?
- Bug dans mscorlib (semble très peu probable)
- Transitoire de corruption de mémoire sur la machine (ce qui est possible, difficile de revenir avec preuve)
- Les autres?
* Mises à jour en réponse à Jordão *
Est-il possible d'appeler la méthode avec un objet qui n'est pas une Identité?
L' ConcurrentDictionary<TKey, TValue>
est tapé tels que TKey
= Identity
et rien ne les sous-classes Identity
. Donc, je ne vois pas comment cela pourrait être possible.
Est-il possible d'appeler la méthode avec null?
Les tests unitaires couvrir le scénario de l'appel de l'ensemble de l' Equals
des implémentations avec la valeur null.
Quelle est la version du code est la trace de la pile d'? Peut-être que certains anciens de la version sensibles à l'exception?
Je suis en analysant le même code qui a généré une exception. J'ai vérifié que la version de l' .NET Framework en cours d'exécution sur mes collègues de l'ordinateur est également 4.0.30319.269.
Tout multithread scénario pourrait provoquer l'exception? Elles sont généralement difficiles à reproduire, mais peut être utile de l'examiner.
Oui, le code est multi-thread et destiné à être. Donc, c'est pourquoi je suis à l'aide d'un ConcurrentDictionary
.
* Suivi liées à la réponse de Jalal Aldeen Asa avait *
J'aurais pensé qu'une situation de concurrence où à un autre thread ensembles x
de null
ne pouvait être la cause, si le paramètre x
a été adoptée par référence à l'aide de la " ref " mot-clé. Je me mis à valider cette théorie avec le code suivant:
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;
}
et le test se termine sans erreur.
Je peux forcer ce comportement si je change la signature de passer x
et y
par référence (c'est - 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.Est égal à(T x, T y)`.