Metode panggilan jika tidak nol di C #

106

Apakah mungkin untuk mempersingkat pernyataan ini?

if (obj != null)
    obj.SomeMethod();

karena saya sering menulis ini dan itu sangat mengganggu. Satu-satunya hal yang dapat saya pikirkan adalah menerapkan pola Objek Null , tetapi bukan itu yang dapat saya lakukan setiap saat dan tentu saja ini bukan solusi untuk mempersingkat sintaks.

Dan masalah serupa dengan acara, di mana

public event Func<string> MyEvent;

lalu panggil

if (MyEvent != null)
    MyEvent.Invoke();
Jakub Arnold
sumber
41
Kami mempertimbangkan untuk menambahkan operator baru ke C # 4: "obj.?SomeMethod ()" berarti "panggil SomeMethod jika obj tidak null, jika tidak, kembalikan null". Sayangnya itu tidak sesuai dengan anggaran kami sehingga kami tidak pernah menerapkannya.
Eric Lippert
@Eric: Apakah komentar ini masih valid? Saya telah melihat tempat itu, tersedia dengan 4.0?
CharithJ
@CharJ: Tidak. Itu tidak pernah diterapkan.
Eric Lippert
3
@CharithJ: Saya mengetahui keberadaan operator penggabungan null. Itu tidak melakukan apa yang diinginkan Darth; dia menginginkan operator akses anggota yang diangkat-ke-null. (Dan omong-omong, komentar Anda sebelumnya memberikan karakterisasi yang salah dari operator penggabungan nol. Anda bermaksud mengatakan "v = m == null? Y: m. Nilai dapat ditulis v = m ?? y".)
Eric Lippert
4
Untuk pembaca yang lebih baru: C # 6.0 mengimplementasikan?., Jadi x? .Y? .Z? .ToString () akan mengembalikan null jika x, y atau z adalah null, atau akan mengembalikan z.ToString () jika tidak ada yang null.
David

Jawaban:

162

Dari C # 6 dan seterusnya, Anda bisa menggunakan:

MyEvent?.Invoke();

atau:

obj?.SomeMethod();

Ini ?.adalah operator yang menyebarkan null, dan akan menyebabkan .Invoke()hubung singkat saat operannya null. Operand hanya diakses sekali, jadi tidak ada risiko masalah "perubahan nilai antara check dan pemanggilan".

===

Sebelum C # 6, tidak: tidak ada sihir yang aman-null, dengan satu pengecualian; metode ekstensi - misalnya:

public static void SafeInvoke(this Action action) {
    if(action != null) action();
}

sekarang ini valid:

Action act = null;
act.SafeInvoke(); // does nothing
act = delegate {Console.WriteLine("hi");}
act.SafeInvoke(); // writes "hi"

Dalam kasus event, ini memiliki keuntungan juga karena menghapus kondisi balapan, yaitu Anda tidak memerlukan variabel sementara. Jadi biasanya Anda membutuhkan:

var handler = SomeEvent;
if(handler != null) handler(this, EventArgs.Empty);

tetapi dengan:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if(handler != null) handler(sender, EventArgs.Empty);
}

kita dapat menggunakan secara sederhana:

SomeEvent.SafeInvoke(this); // no race condition, no null risk
Marc Gravell
sumber
1
Saya sedikit berkonflik tentang ini. Dalam kasus Action atau event handler - yang biasanya lebih independen - ini masuk akal. Saya tidak akan merusak enkapsulasi untuk metode biasa. Ini menyiratkan membuat metode dalam kelas statis yang terpisah dan menurut saya kehilangan enkapsulasi dan penurunan keterbacaan / organisasi kode secara keseluruhan sepadan dengan sedikit peningkatan dalam keterbacaan lokal
tvanfosson
@tvanon - memang; tapi maksud saya adalah bahwa ini adalah satu-satunya kasus yang saya tahu di mana itu akan berhasil. Dan pertanyaan itu sendiri mengangkat topik delegasi / acara.
Marc Gravell
Kode itu akhirnya menghasilkan metode anonim di suatu tempat, yang benar-benar mengacaukan pelacakan tumpukan dari pengecualian apa pun. Apakah mungkin memberi nama metode anonim? ;)
sisve
Salah sesuai 2015 / C # 6.0 ...? apakah dia soltion. ReferenceTHatMayBeNull? .CallMethod () tidak akan memanggil metode ini jika null.
TomTom
1
@mercu seharusnya ?.- dalam VB14 dan lebih tinggi
Marc Gravell
27

Apa yang Anda cari adalah Null Bersyarat (tidak "penggabungan") Operator: ?.. Ini tersedia mulai C # 6.

Contoh Anda adalah obj?.SomeMethod();. Jika obj nol, tidak ada yang terjadi. Ketika metode memiliki argumen, misalnya obj?.SomeMethod(new Foo(), GetBar());argumen tidak dievaluasi jika objnol, yang penting jika mengevaluasi argumen akan memiliki efek samping.

Dan perangkaian dimungkinkan: myObject?.Items?[0]?.DoSomething()

Vimes
sumber
1
Ini fantastis. Perlu dicatat bahwa ini adalah fitur C # 6 ... (Yang akan tersirat dari pernyataan VS2015 Anda, tetapi masih perlu diperhatikan). :)
Kyle Goode
10

Metode ekstensi cepat:

    public static void IfNotNull<T>(this T obj, Action<T> action, Action actionIfNull = null) where T : class {
        if(obj != null) {
            action(obj);
        } else if ( actionIfNull != null ) {
            actionIfNull();
        }
    }

contoh:

  string str = null;
  str.IfNotNull(s => Console.Write(s.Length));
  str.IfNotNull(s => Console.Write(s.Length), () => Console.Write("null"));

atau sebagai alternatif:

    public static TR IfNotNull<T, TR>(this T obj, Func<T, TR> func, Func<TR> ifNull = null) where T : class {
        return obj != null ? func(obj) : (ifNull != null ? ifNull() : default(TR));
    }

contoh:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));
katbyte.dll
sumber
Saya mencoba melakukannya dengan metode ekstensi dan saya berakhir dengan kode yang hampir sama. Namun, masalah dengan implementasi ini adalah implementasi ini tidak akan berfungsi dengan ekspresi yang mengembalikan tipe nilai. Jadi harus punya metode kedua untuk itu.
orad
4

Acara dapat diinisialisasi dengan delegasi default kosong yang tidak pernah dihapus:

public event EventHandler MyEvent = delegate { };

Tidak perlu pemeriksaan null.

[ Pembaruan , terima kasih kepada Bevan karena telah menunjukkan hal ini]

Waspadai kemungkinan dampak kinerja. Tolok ukur mikro cepat yang saya lakukan menunjukkan bahwa menangani acara tanpa pelanggan 2-3 kali lebih lambat saat menggunakan pola "delegasi default". (Pada laptop 2.5GHz dual core saya, artinya 279ms: 785ms untuk meningkatkan 50 juta acara yang tidak berlangganan.). Untuk hot spot aplikasi, itu mungkin menjadi masalah untuk dipertimbangkan.

Sven Künzler
sumber
1
Jadi, Anda menghindari pemeriksaan nol dengan memanggil delegasi kosong ... pengorbanan memori dan waktu yang terukur untuk menghemat beberapa penekanan tombol? YMMV, tapi bagi saya, pertukaran yang buruk.
Bevan
Saya juga telah melihat tolok ukur yang menunjukkan bahwa jauh lebih mahal untuk mengadakan acara dengan banyak delegasi yang berlangganan daripada hanya dengan satu.
Greg D
2

Ini artikel oleh Ian Griffiths memberikan dua solusi yang berbeda untuk masalah yang ia menyimpulkan trik rapi yang Anda tidak harus menggunakan.

Darrel Miller
sumber
3
Dan berbicara sebagai penulis artikel itu, saya akan menambahkan bahwa Anda pasti tidak boleh menggunakannya sekarang karena C # 6 menyelesaikan ini di luar kotak dengan operator bersyarat nol (?. Dan. []).
Ian Griffiths
2

Metode perpanjangan cerating seperti yang disarankan tidak benar-benar menyelesaikan masalah dengan kondisi ras, melainkan menyembunyikannya.

public static void SafeInvoke(this EventHandler handler, object sender)
{
    if (handler != null) handler(sender, EventArgs.Empty);
}

Seperti yang dinyatakan kode ini adalah padanan elegan untuk solusi dengan variabel sementara, tapi ...

Masalah dengan keduanya adalah mungkin pelanggan acara dapat dipanggil SETELAH itu telah berhenti berlangganan dari acara tersebut . Hal ini dimungkinkan karena unsubscription dapat terjadi setelah instance delegasi disalin ke variabel temp (atau diteruskan sebagai parameter dalam metode di atas), tetapi sebelum delegasi dipanggil.

Secara umum, perilaku kode klien tidak dapat diprediksi dalam kasus seperti ini: status komponen tidak dapat mengizinkan untuk menangani pemberitahuan kejadian. Mungkin saja untuk menulis kode klien sebagai cara untuk menanganinya, tetapi itu akan menempatkan tanggung jawab yang tidak perlu kepada klien.

Satu-satunya cara yang diketahui untuk memastikan keamanan utas adalah dengan menggunakan pernyataan kunci untuk pengirim acara. Ini memastikan bahwa semua subscription \ unsubscriptions \ invocation adalah serial.

Agar lebih akurat, kunci harus diterapkan ke objek sinkronisasi yang sama yang digunakan dalam metode add \ remove event accessor yang menjadi default 'ini'.

andrey.tsykunov
sumber
Ini bukan kondisi balapan yang dirujuk. Kondisi balapan adalah jika (MyEvent! = Null) // MyEvent tidak null sekarang MyEvent.Invoke (); // MyEvent null sekarang, hal buruk terjadi Jadi dengan kondisi balapan ini, saya tidak bisa menulis event handler async yang dijamin berfungsi. Namun dengan 'kondisi balapan' Anda, saya dapat menulis penangan peristiwa asinkron yang dijamin berfungsi. Tentu saja panggilan ke pelanggan dan unsubscriber harus sinkron, atau kode kunci diperlukan di sana.
jyoung
Memang. Analisis saya tentang masalah ini diposting di sini: blogs.msdn.com/ericlippert/archive/2009/04/29/…
Eric Lippert
2

Saya setuju dengan jawaban Kenny Eliasson. Pergi dengan metode Ekstensi. Berikut adalah ikhtisar singkat tentang metode ekstensi dan metode IfNotNull yang Anda perlukan.

Metode Ekstensi (metode IfNotNull)

Rohit
sumber
1

Mungkin tidak lebih baik tetapi menurut saya lebih mudah dibaca adalah dengan membuat metode ekstensi

public static bool IsNull(this object obj) {
 return obj == null;
}
Kenny Eliasson
sumber
1
apa return obj == nullmaksudnya Apa yang akan dikembalikan
Hammad Khan
1
Ini berarti bahwa jika objadalah nullmetode akan kembali true, saya pikir.
Joel
1
Bagaimana ini menjawab pertanyaan itu?
Kugel
Terlambat membalas, tetapi apakah Anda yakin ini akan berhasil? Bisakah Anda memanggil metode ekstensi pada objek itu null? Saya cukup yakin ini tidak berfungsi dengan metode tipe itu sendiri, jadi saya ragu itu akan berfungsi dengan metode ekstensi juga. Saya percaya cara terbaik untuk memeriksa apakah sebuah objek adalah null adalah obj is null. Sayangnya, untuk memeriksa apakah sebuah objek tidak tidak nullmembutuhkan pembungkus dalam tanda kurung, yang sangat disayangkan.
natiiix