(ini == null) dalam C #!

129

Karena bug yang diperbaiki di C # 4, program berikut dicetak true. (Cobalah di LINQPad)

void Main() { new Derived(); }

class Base {
    public Base(Func<string> valueMaker) { Console.WriteLine(valueMaker()); }
}
class Derived : Base {
    string CheckNull() { return "Am I null? " + (this == null); }
    public Derived() : base(() => CheckNull()) { }
}

Dalam VS2008 dalam mode Rilis, ia melempar InvalidProgramException. (Dalam mode Debug, itu berfungsi dengan baik)

Dalam VS2010 Beta 2, itu tidak dikompilasi (saya tidak mencoba Beta 1); Saya mempelajarinya secara susah-payah

Apakah ada cara lain untuk membuat this == nullC # murni?

Slaks
sumber
3
Kemungkinan besar itu adalah bug di kompiler C # 3.0. Ini bekerja seperti seharusnya di C # 4.0.
Mehrdad Afshari
82
@SLaks: Masalah dengan bug adalah Anda dapat mengharapkan mereka diperbaiki di beberapa titik sehingga menemukan mereka "berguna" mungkin tidak bijaksana.
AnthonyWJones
6
Terima kasih! tidak tahu tentang LINQPad. itu keren!
durï
8
Dengan cara apa tepatnya ini bermanfaat?
Allen Rice
6
bagaimana bug ini bermanfaat?
BlackTigerX

Jawaban:

73

Pengamatan ini telah diposting di StackOverflow di pertanyaan lain sebelumnya hari ini.

Marc 's jawaban yang bagus untuk pertanyaan itu menunjukkan bahwa menurut spec (bagian 7.5.7), Anda tidak harus dapat akses thisdalam konteks dan kemampuan untuk melakukannya di C # 3.0 compiler bug. Kompiler C # 4.0 berperilaku benar sesuai dengan spesifikasi (bahkan dalam Beta 1, ini adalah kesalahan waktu kompilasi):

§ 7.5.7 Akses ini

A ini-akses terdiri dari kata reserved this.

akses ini:

this

A ini-akses hanya diperbolehkan dalam blok dari sebuah contoh konstruktor, metode contoh, atau sebuah contoh accessor.

Mehrdad Afshari
sumber
2
Saya tidak melihat, mengapa dalam kode yang disajikan dalam pertanyaan ini, penggunaan kata kunci "ini" tidak valid. Metode CheckNull adalah metode instance normal, nonstatic . Menggunakan "ini" adalah 100% valid dalam metode tersebut, dan bahkan membandingkan ini dengan nol adalah valid. Kesalahan ada di baris init dasar: itu adalah upaya untuk melewati delegasi terikat-instance sebagai parameter ke ctor basis. Ini adalah bug (lubang dalam pemeriksaan sematis) di kompiler: itu TIDAK mungkin terjadi. Anda tidak diperbolehkan menulis : base(CheckNull())jika CheckNull tidak statis, dan Anda juga seharusnya tidak dapat menyejajarkan lambda yang terhubung dengan instance.
quetzalcoatl
4
@quetzalcoatl: thisdalam CheckNullmetode legal Apa yang tidak legal adalah implisit ini-akses di () => CheckNull(), pada dasarnya () => this.CheckNull(), yang berjalan di luar blok dari sebuah contoh konstruktor. Saya setuju bahwa bagian dari spesifikasi yang saya kutip sebagian besar terfokus pada legalitas sintaksis thiskata kunci, dan mungkin bagian lain membahas masalah ini dengan lebih tepat, tetapi mudah untuk secara ekstrapolasi secara konseptual dari bagian spesifikasi ini juga.
Mehrdad Afshari
2
Maaf, saya tidak setuju. Meskipun saya tahu itu (dan menulisnya di komentar di atas) dan Anda juga tahu itu - Anda belum menyebutkan penyebab sebenarnya dari masalah dalam jawaban Anda (diterima). Jawabannya diterima - jadi sepertinya penulis juga mengganggunya. Tapi saya ragu bahwa semua pembaca akan seterang dan fasih dalam lambdas untuk mengenali instancebound-lambda versus static-lambda pada pandangan pertama dan memetakan itu untuk 'ini' dan masalah dengan IL yang dipancarkan :) Inilah sebabnya saya menambahkan tiga sen saya. Selain itu, saya setuju dengan semua hal lain yang ditemukan, dianalisis, dan dijelaskan oleh Anda dan orang lain :)
quetzalcoatl
24

Dekompilasi mentah (Reflektor tanpa optimasi) dari biner mode Debug adalah:

private class Derived : Program.Base
{
    // Methods
    public Derived()
    {
        base..ctor(new Func<string>(Program.Derived.<.ctor>b__0));
        return;
    }

    [CompilerGenerated]
    private static string <.ctor>b__0()
    {
        string CS$1$0000;
        CS$1$0000 = CS$1$0000.CheckNull();
    Label_0009:
        return CS$1$0000;
    }

    private string CheckNull()
    {
        string CS$1$0000;
        CS$1$0000 = "Am I null? " + ((bool) (this == null));
    Label_0017:
        return CS$1$0000;
    }
}

Metode CompilerGenerated tidak masuk akal; jika Anda melihat IL (di bawah), itu memanggil metode pada string nol (!).

   .locals init (
        [0] string CS$1$0000)
    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull()
    L_0006: stloc.0 
    L_0007: br.s L_0009
    L_0009: ldloc.0 
    L_000a: ret 

Dalam mode Rilis, variabel lokal dioptimalkan menjauh, sehingga mencoba untuk mendorong variabel yang tidak ada ke stack.

    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull()
    L_0006: ret 

(Reflektor lumpuh saat mengubahnya menjadi C #)


EDIT : Apakah ada orang (Eric Lippert?) Yang tahu mengapa kompiler memancarkan ldloc?

Slaks
sumber
11

Saya sudah memilikinya! (dan mendapat bukti juga)

teks alternatif

leppie
sumber
2
Terlambat, itu pertanda aku harus berhenti coding :) Meretas barang DLR IIRC kami.
leppie
buat visualizer debugger (DebuggerDisplay) untuk apa pun 'ini', dan buat itu membodohi Anda itu nol? : D just sayin '
10

Ini bukan "bug". Ini adalah Anda menyalahgunakan sistem tipe. Anda seharusnya tidak pernah memberikan referensi ke instance saat ini ( this) kepada siapa pun dalam konstruktor.

Saya bisa membuat "bug" serupa dengan memanggil metode virtual di dalam konstruktor kelas dasar juga.

Hanya karena Anda dapat melakukan sesuatu yang buruk tidak berarti itu bug ketika Anda mendapatkannya sedikit.


sumber
14
Ini adalah bug penyusun. Ini menghasilkan IL tidak valid. (Baca jawaban saya)
Membalas
Konteksnya statis, jadi Anda seharusnya tidak diperbolehkan referensi metode instance pada tahap itu.
leppie
10
@ Will: Ini adalah bug kompiler. Kompiler seharusnya menghasilkan kode yang valid dan dapat diverifikasi untuk cuplikan kode itu atau meludahkan pesan kesalahan. Ketika kompiler tidak berperilaku sesuai dengan spesifikasi, itu buggy .
Mehrdad Afshari
2
@ Will # 4: Ketika saya menulis kode, saya belum memikirkan implikasinya. Saya hanya menyadari bahwa itu tidak masuk akal ketika berhenti mengkompilasi di VS2010. -
Membalas
3
Omong-omong, panggilan metode virtual dalam konstruktor adalah operasi yang sepenuhnya valid. Itu tidak direkomendasikan. Ini dapat menyebabkan bencana logis tetapi tidak pernah terjadi InvalidProgramException.
Mehrdad Afshari
3

Saya bisa saja salah, tapi saya cukup yakin jika objek Anda nulltidak akan ada skenario di mana thisberlaku.

Misalnya, bagaimana Anda menelepon CheckNull?

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException
Dan Tao
sumber
3
Dalam lambda dalam argumen konstruktor. Baca seluruh cuplikan kode. (Dan coba jika Anda tidak percaya kepada saya)
Slaks
Saya setuju walaupun saya masih ingat sedikit tentang bagaimana dalam C ++ suatu objek tidak memiliki referensi di dalam konstruktornya dan saya bertanya-tanya apakah skenario (ini == null) digunakan dalam kasus-kasus itu untuk memeriksa apakah panggilan ke suatu metode adalah dibuat dari konstruktor objek sebelum mengekspos pointer ke "ini". Padahal, sejauh yang saya tahu dalam C #, tidak boleh ada kasus di mana "ini" akan menjadi nol, bahkan tidak dalam Buang atau metode finalisasi.
jpierson
Saya kira maksud saya adalah bahwa ide thisyang saling eksklusif dari kemungkinan menjadi null - semacam "Cogito, ergo sum" pemrograman komputer. Oleh karena itu keinginan Anda untuk menggunakan ekspresi this == nulldan mengembalikannya benar menurut saya sebagai salah arah.
Dan Tao
Dengan kata lain: Saya memang membaca kode Anda; apa yang saya katakan adalah bahwa saya mempertanyakan apa yang Anda coba capai sejak awal.
Dan Tao
Kode ini hanya menunjukkan bug, dan, seperti yang Anda tunjukkan, sama sekali tidak berguna. Untuk melihat kode yang sangat berguna, bacalah jawaban kedua saya.
Membalas
-1

Tidak yakin apakah ini yang Anda cari

    public static T CheckForNull<T>(object primary, T Default)
    {
        try
        {
            if (primary != null && !(primary is DBNull))
                return (T)Convert.ChangeType(primary, typeof(T));
            else if (Default.GetType() == typeof(T))
                return Default;
        }
        catch (Exception e)
        {
            throw new Exception("C:CFN.1 - " + e.Message + "Unexpected object type of " + primary.GetType().ToString() + " instead of " + typeof(T).ToString());
        }
        return default(T);
    }

contoh: UserID = CheckForNull (Request.QueryString ["UserID"], 147);

Scott dan Tim Pengembang
sumber
13
Anda benar-benar salah mengerti pertanyaan itu.
SLaks
1
Saya pikir banyak. Kupikir aku akan tetap mencoba.
Scott dan Tim Pengembang