Pertanyaan Mustahil NullReferenceException?


Saya sedang menyelidiki pengecualian bahwa seorang rekan baru saja mendapat saat menjalankan aplikasi melalui 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) 

Menggunakan .NET Reflector, Saya telah melihat kode untuk
GenericEqualityComparer<T>.Equals(T x, T y), dan saya tidak bisa melihat kemungkinan penyebab 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;
}

Tipe dari T,  TKey dan TIdentity semua tipe yang sama dalam hal ini jejak stack.

Jenisnya adalah tipe kustom yang disebut Identity yang mengimplementasikan IEquatable<Identity>. Ini tidak dapat diubah dan tidak dapat dibangun dengan nilai nol untuk bidang yang digunakan dalam penerapannya Equals(Identity other). Ini juga menimpa Equals(object obj) seperti ini:

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

Saya memiliki serangkaian tes unit yang cukup lengkap di sekitar Equals implementasi. Jadi, dengan senang hati akan menerima nilai nol untuk yang lain / obj dan mengembalikan false seperti yang diharapkan.

Jenisnya tidak menimpa == operator atau != operator.

Meski begitu, saya berharap untuk melihat kelas saya di atas tumpukan jejak jika pengecualian itu terlempar dari pelaksanaan Equals(Identity other) di saya Identity kelas, tetapi ia mengatakan NullReferenceException berasal mscorlib.

Saya sedang menjalankan .NET Framework versi 4.0.30319.269.

Saya tidak memiliki dump memori, dan saya belum pernah melihat ini sebelumnya dan belum pernah mereproduksinya. Namun, saya berkewajiban untuk menyelidiki dan benar-benar yakin bahwa itu tidak disebabkan oleh kode kami dan itu tidak akan terjadi dalam produksi.

Jadi, pertanyaan sebenarnya adalah: Apa yang menyebabkan pengecualian ini?

  • Bug di mscorlib (sepertinya sangat tidak mungkin)
  • Korupsi memori sementara pada mesin (mungkin, sulit untuk membuat cadangan bukti)
  • Lain?

* Pembaruan sebagai tanggapan terhadap Jordão *

Apakah mungkin untuk memanggil metode dengan objek yang bukan Identitas?

Itu ConcurrentDictionary<TKey, TValue> diketik seperti itu TKey = Identity dan tidak ada subclass Identity. Jadi, saya tidak bisa melihat bagaimana itu bisa terjadi.

Apakah mungkin untuk memanggil metode dengan null?

Tes unit mencakup skenario memanggil semua Equals implementasi dengan nol.

Versi kode apa dari tumpukan jejak itu? Mungkin beberapa versi lama rentan terhadap pengecualian?

Saya menganalisis kode yang sama yang menghasilkan pengecualian. Saya telah memeriksa bahwa versi .NET Framework yang berjalan di komputer rekan saya juga 4.0.30319.269.

Skenario multithread dapat menyebabkan pengecualian? Ini biasanya sulit untuk direproduksi, tetapi mungkin perlu diselidiki.

Ya, kode ini multi-berulir dan dimaksudkan untuk menjadi. Jadi, itulah mengapa saya menggunakan ConcurrentDictionary.

* Tindak lanjut terkait dengan respons dari Jalal Aldeen Saa'd *

Saya akan berpikir bahwa kondisi balapan di mana beberapa rangkaian lainnya x untuk null hanya bisa menjadi penyebab jika parameter x dilewatkan oleh referensi menggunakan kata kunci 'ref'. Saya menetapkan untuk memvalidasi teori itu dengan kode berikut:

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

dan tes selesai tanpa kesalahan.

Saya dapat memaksa perilaku itu jika saya mengubah tanda tangan untuk lulus x dan ydengan referensi (yaitu, 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) `.


32
2017-10-25 00:52


asal


Jawaban:


Saya akan meletakkan hipotesis saya di sini.

Tumpukan ini menuntun Anda untuk percaya bahwa ini adalah tempat terjadinya kecelakaan, tetapi terjadi di tempat lain. Kami sedang melihat benang yang salah.

Saya tidak tahu apakah ini akan praktis, tetapi terkadang "debugging printf" yang lama membantu. Bagaimana jika Anda mencetak nilai yang Anda cari sebelum menelepon TryGetValue? Anda akan melihat apakah Anda menyerang nol atau tidak.


4
2017-10-25 11:49



Saya menemukan pengecualian referensi nol di Setara tentang beberapa tahun yang lalu (tidak yakin apakah itu di 3.5 atau 4.0, atau jika itu pernah diperbaiki). Tidak jelas bagi saya jenis apa yang dibandingkan dalam kasus Anda, tetapi dalam situasi saya itu akan terjadi setiap kali membandingkan objek refleksi MethodInfo untuk deklarasi metode generik ke objek APAPUN non-MethodInfo ... Ka-boom! Jadi, jika Anda membandingkan objek refleksi, ini bisa menjadi itu. Jika Anda tidak, setidaknya saya dapat memberi kesaksian tentang fakta bahwa setidaknya ada satu implementasi Setara dalam BCL yang dapat membuang pengecualian referensi tidak ada alasan yang baik dalam situasi tertentu, jadi mungkin ada orang lain. Bahkan yang suci. NET BCL masih merupakan perangkat lunak, dan SEMUA perangkat lunak memiliki bug.


1
2017-10-31 00:10