Di C #, apa yang terjadi ketika Anda memanggil metode ekstensi pada objek nol?

329

Apakah metode dipanggil dengan nilai nol atau apakah memberikan pengecualian referensi nol?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

Jika demikian, saya tidak perlu memeriksa parameter 'ini' untuk nol?

kekuatan
sumber
Kecuali tentu saja, Anda berurusan dengan ASP.NET MVC yang akan membuang kesalahan ini Cannot perform runtime binding on a null reference.
Mrchief

Jawaban:

387

Itu akan bekerja dengan baik (tidak terkecuali). Metode ekstensi tidak menggunakan panggilan virtual (yaitu menggunakan instruksi "panggilan", bukan "callvirt") sehingga tidak ada pemeriksaan nol kecuali Anda menulisnya sendiri dalam metode ekstensi. Ini sebenarnya berguna dalam beberapa kasus:

public static bool IsNullOrEmpty(this string value)
{
    return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull<T>(this T obj, string parameterName)
        where T : class
{
    if(obj == null) throw new ArgumentNullException(parameterName);
}

dll

Pada dasarnya, panggilan ke panggilan statis sangat harfiah - yaitu

string s = ...
if(s.IsNullOrEmpty()) {...}

menjadi:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

di mana jelas tidak ada cek nol.

Marc Gravell
sumber
1
Marc, Anda sedang berbicara tentang panggilan "virtual" - tetapi hal yang sama berlaku untuk panggilan nonvirtual pada metode instan. Saya pikir kata "virtual" di sini salah tempat.
Konrad Rudolph
6
@ Konrad: Itu tergantung pada konteksnya. Kompiler C # biasanya menggunakan callvirt bahkan untuk metode non-virtual, tepatnya untuk mendapatkan cek nol.
Jon Skeet
Saya merujuk pada perbedaan antara instruksi panggilan dan panggilan telepon. Dalam satu mengedit Aku benar-benar mencoba untuk href dua halaman Opcodes, tapi editor barfed di link ...
Marc Gravell
2
Saya tidak melihat bagaimana penggunaan metode ekstensi ini bisa berguna, sungguh. Hanya karena itu bisa dilakukan bukan berarti itu benar, dan seperti yang Binary Worrier sebutkan di bawah, bagi saya itu kelihatannya lebih seperti penyimpangan.
Perangkap
3
@Trap: fitur ini sangat bagus jika Anda menyukai pemrograman gaya fungsional.
Roy Tinker
50

Selain jawaban yang benar dari Marc Gravell.

Anda bisa mendapatkan peringatan dari kompiler jika jelas bahwa argumen ini nol:

default(string).MyExtension();

Bekerja dengan baik saat runtime, tetapi menghasilkan peringatan "Expression will always cause a System.NullReferenceException, because the default value of string is null".

Stefan Steinegger
sumber
32
Mengapa ini memperingatkan "selalu menyebabkan System.NullReferenceException". Padahal sebenarnya, itu tidak akan pernah terjadi?
tpower
46
Untungnya, kami programer hanya peduli tentang kesalahan, bukan peringatan: p
JulianR
7
@JulianR: Ya, ada yang, ada yang tidak. Dalam konfigurasi build rilis kami, kami memperlakukan peringatan sebagai kesalahan. Jadi itu tidak berhasil.
Stefan Steinegger
8
Terima kasih atas catatannya; Saya akan mendapatkan ini di database bug dan kita akan melihat apakah kita bisa memperbaikinya untuk C # 4.0. (Tidak ada janji - karena ini adalah kasus sudut yang tidak realistis dan hanya peringatan, kita mungkin menyepakatinya untuk memperbaikinya.)
Eric Lippert
3
@Stefan: Karena ini adalah bug dan bukan peringatan "benar", Anda bisa menggunakan pernyataan #pragma untuk menekan peringatan agar kode lolos dari build rilis Anda.
Martin RL
25

Seperti yang sudah Anda temukan, karena metode ekstensi hanya metode statis dimuliakan, mereka akan dipanggil dengan nullreferensi yang dilewatkan, tanpa NullReferenceExceptiondilemparkan. Tapi, karena mereka terlihat seperti metode instan untuk penelepon, mereka juga harus bersikap seperti itu. Maka Anda harus, sebagian besar waktu, memeriksathis parameter dan melemparkan pengecualian jika itu null. Tidak apa-apa untuk melakukan ini jika metode secara eksplisit menangani nullnilai - nilai dan namanya menunjukkan itu sebagaimana mestinya, seperti dalam contoh di bawah ini:

public static class StringNullExtensions { 
  public static bool IsNullOrEmpty(this string s) { 
    return string.IsNullOrEmpty(s); 
  } 
  public static bool IsNullOrBlank(this string s) { 
    return s == null || s.Trim().Length == 0; 
  } 
}

Saya juga menulis posting blog tentang ini beberapa waktu yang lalu.

Jordão
sumber
3
Memilih ini karena benar dan masuk akal bagi saya (dan ditulis dengan baik), sementara saya juga lebih suka penggunaan yang dijelaskan dalam jawaban oleh @Marc Gravell.
qxotk
17

Nol akan diteruskan ke metode ekstensi.

Jika metode ini mencoba mengakses objek tanpa memeriksa apakah itu nol, maka ya, itu akan melempar pengecualian.

Seorang pria di sini menulis metode ekstensi "IsNull" dan "IsNotNull" yang memeriksa apakah referensi yang diberikan adalah nol atau tidak. Secara pribadi saya pikir ini adalah penyimpangan dan seharusnya tidak terlihat terang hari, tapi itu benar-benar valid c #.

Biner Terburuk
sumber
18
Memang bagi saya itu seperti bertanya pada mayat "Apakah Anda masih hidup" dan mendapat jawaban "tidak". Mayat tidak dapat menjawab pertanyaan apa pun, Anda juga tidak dapat "memanggil" metode pada objek nol.
Binary Worrier
14
Saya tidak setuju dengan logika Binary Worrier, karena sangat berguna untuk memanggil ekstensi tanpa khawatir tentang referensi nol, tetapi +1 untuk nilai komedi analogi :-)
Tim Abell
10
Sebenarnya, kadang-kadang Anda tidak tahu apakah seseorang sudah mati, jadi Anda masih bertanya, dan orang itu mungkin menjawab, "tidak, hanya beristirahat dengan mata tertutup"
nurchi
7
Ketika Anda perlu rantai beberapa operasi (katakan 3+), Anda dapat (dengan asumsi tidak ada efek samping) mengubah beberapa baris kode null-checking boilerplate membosankan menjadi satu-liner dirantai elegan dengan metode ekstensi "null-safe". (Mirip dengan yang disarankan ".?" - operator, tetapi diakui tidak elegan.) Jika tidak jelas ekstensi adalah "null-safe" Saya biasanya awalan metode dengan "Aman", jadi jika misalnya itu adalah salinan- metode, namanya bisa "SafeCopy", dan itu akan mengembalikan nol jika argumen itu nol.
AnorZaken
3
Saya tertawa sangat keras dengan jawaban @BinaryWorrier hahahaha saya melihat diri saya menendang tubuh untuk memeriksa apakah sudah mati atau tidak hahaha Jadi dalam imajinasi SAYA, yang melakukan pengecekan apakah tubuh itu mati atau tidak adalah saya dan bukan tubuh itu sendiri, implementasi untuk cek ada di saya, menendang secara aktif untuk melihat apakah itu bergerak. Jadi sebuah badan tidak tahu apakah itu mati atau tidak, WHO memeriksa, tahu, sekarang Anda bisa berargumen bahwa Anda bisa "menyambungkan" ke tubuh dengan cara agar tubuh Anda memberi tahu Anda apakah itu mati atau tidak dan menurut pendapat saya adalah apa Ekstensi untuk.
Zorkind
7

Seperti yang ditunjukkan orang lain, memanggil metode ekstensi pada referensi nol menyebabkan argumen ini menjadi nol dan tidak ada hal lain yang akan terjadi. Ini memunculkan ide untuk menggunakan metode ekstensi untuk menulis klausa penjaga.

Anda dapat membaca artikel ini sebagai contoh: Cara Mengurangi Kompleksitas Siklon: Penjaga Klausa Versi singkatnya adalah ini:

public static class StringExtensions
{
    public static void AssertNonEmpty(this string value, string paramName)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Value must be a non-empty string.", paramName);
    }
}

Ini adalah metode ekstensi kelas string yang dapat dipanggil dengan referensi nol:

((string)null).AssertNonEmpty("null");

Panggilan berfungsi dengan baik hanya karena runtime akan berhasil memanggil metode ekstensi dengan referensi nol. Kemudian Anda dapat menggunakan metode ekstensi ini untuk menerapkan klausa penjaga tanpa sintaksis yang berantakan:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }
Zoran Horvat
sumber
3

Metode ekstensi statis, jadi jika Anda tidak melakukan apa pun pada MyObject ini seharusnya tidak menjadi masalah, tes cepat harus memverifikasinya :)

Fredrik Leijon
sumber
-1

Ada beberapa aturan emas ketika Anda ingin agar Anda dapat dibaca dan vertikal.

  • salah satu yang layak dikatakan dari Eiffel mengatakan kode spesifik yang dienkapsulasi ke dalam suatu metode harus bekerja melawan beberapa input, kode tersebut dapat diterapkan jika memenuhi beberapa prasyarat dan memastikan output yang diharapkan

Dalam kasus Anda - DesignByContract rusak ... Anda akan melakukan beberapa logika pada instance nol.

ruslander
sumber