C # 'da boş değilse yöntem çağrısı


107

Bu ifadeyi bir şekilde kısaltmak mümkün mü?

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

çünkü bunu çok yazıyorum ve oldukça sinir bozucu oluyor. Aklıma gelen tek şey, Boş Nesne kalıbını uygulamaktır , ancak her seferinde yapabileceğim şey bu değil ve kesinlikle sözdizimini kısaltmak için bir çözüm değil.

Ve olaylarla ilgili benzer bir sorun, nerede

public event Func<string> MyEvent;

ve sonra çağır

if (MyEvent != null)
    MyEvent.Invoke();

41
C # 4'e yeni bir operatör eklemeyi düşündük: "obj.?SomeMethod ()", "obj null değilse SomeMethod'u çağır, aksi takdirde null döndür" anlamına gelir. Maalesef bütçemize uymadığı için hiç uygulamadık.
Eric Lippert

@Eric: Bu yorum hala geçerli mi? 4.0 ile kullanılabilir olduğunu bir yerde gördüm?
CharithJ

@CharithJ: Hayır. Asla uygulanmadı.
Eric Lippert

3
@CharithJ: Boş birleştirme operatörünün varlığının farkındayım. Darth'ın istediğini yapmaz; kaldırılarak sıfırlanabilen üye erişim operatörü istiyor. (Ve bu arada, önceki yorumunuz boş birleştirme operatörünün yanlış bir karakterizasyonunu veriyor. "V = m == null? Y: m. Değer yazılabilir v = m ?? y" demek istediniz.)
Eric Lippert

4
Yeni okuyucular için: C # 6.0?. Uygular, bu nedenle x? .Y? .Z? .ToString (), x, y veya z null ise null döndürür veya hiçbiri boş değilse z.ToString () döndürür.
David

Yanıtlar:


166

C # 6'dan itibaren şunları kullanabilirsiniz:

MyEvent?.Invoke();

veya:

obj?.SomeMethod();

?.Boş yayılan operatörüdür ve neden olacaktır .Invoke()işlenen olduğunda kısa devre edilecek null. İşlenene yalnızca bir kez erişilir, bu nedenle "kontrol ve çağırma arasında değer değişikliği" sorunu riski yoktur.

===

C # 6'dan önce, hayır: Bir istisna dışında sıfır güvenli sihir yoktur; uzantı yöntemleri - örneğin:

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

şimdi bu geçerlidir:

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

Olaylar söz konusu olduğunda, bunun aynı zamanda yarış koşulunu ortadan kaldırma avantajı vardır, yani geçici bir değişkene ihtiyacınız yoktur. Normalde ihtiyacınız olacak:

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

fakat:

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

basitçe kullanabiliriz:

SomeEvent.SafeInvoke(this); // no race condition, no null risk

1
Bu konuda biraz çelişki içindeyim. Genellikle daha bağımsız olan bir Eylem veya olay işleyici söz konusu olduğunda bu biraz mantıklıdır. Yine de normal bir yöntem için kapsüllemeyi bozmam. Bu, yöntemi ayrı, statik bir sınıfta oluşturmak anlamına geliyor ve genel olarak kapsüllemeyi kaybetmenin ve okunabilirliği / kod organizasyonunu
azaltmanın

@tvanfosson - gerçekten; ama benim açımdan, bunun nerede işe yarayacağını bildiğim tek durum bu. Ve sorunun kendisi delegelerin / olayların konusunu gündeme getiriyor.
Marc Gravell

Bu kod, herhangi bir istisnanın yığın izini gerçekten bozan bir yerde anonim bir yöntem üretir. Anonim yöntem isimleri vermek mümkün mü? ;)
sisve

@MarcGravell buna boş Koşullu işleç adı verilmez.
RayLoveless

1
@mercu ?.- VB14 ve üzeri sürümlerde
Marc Gravell

27

Ne aradığınız olan Null-Koşullu operatör ( "kaynaştırma" değil): ?.. C # 6'dan itibaren mevcuttur.

Örneğiniz olacaktır obj?.SomeMethod();. Obj null ise hiçbir şey olmaz. Metodun argümanları olduğunda, örneğin obj?.SomeMethod(new Foo(), GetBar());argümanların objboş olup olmadığı değerlendirilmez, argümanların değerlendirilmesinin yan etkileri olacaksa önemlidir.

Ve zincirleme mümkündür: myObject?.Items?[0]?.DoSomething()


1
Bu fantastik. Bunun bir C # 6 özelliği olduğunu belirtmekte fayda var ... (VS2015 ifadenizden ima edilecek, ancak yine de kayda değer). :)
Kyle Goode

10

Hızlı bir uzatma yöntemi:

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

misal:

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

Veya alternatif olarak:

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

misal:

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

Bunu uzantı yöntemleriyle yapmaya çalıştım ve hemen hemen aynı kodla sonuçlandım. Ancak bu uygulamadaki sorun, bir değer türü döndüren ifadelerle çalışmamasıdır. Bu yüzden bunun için ikinci bir yönteme sahip olmak gerekiyordu.
orad

4

Olaylar, hiçbir zaman kaldırılmayan boş bir varsayılan delege ile başlatılabilir:

public event EventHandler MyEvent = delegate { };

Boş kontrol gerekli değildir.

[ Güncelleme , bunu işaret ettiği için Bevan'a teşekkürler]

Yine de olası performans etkisinin farkında olun. Yaptığım hızlı bir mikro kıyaslama, "varsayılan delege" modelini kullanırken abonesi olmayan bir olayı işlemenin 2-3 kat daha yavaş olduğunu gösteriyor. (Çift çekirdekli 2.5GHz dizüstü bilgisayarımda bu, abone olunmayan 50 milyon etkinliği artırmak için 279ms: 785ms anlamına geliyor.). Uygulama sıcak noktaları için bu, dikkate alınması gereken bir sorun olabilir.


1
Öyleyse, boş bir delege çağırarak bir sıfır kontrolünden kaçınıyorsunuz ... birkaç tuş vuruşunu kaydetmek için hem hafızanın hem de zamandan ölçülebilir bir fedakarlık mı? YMMV, ama benim için kötü bir takas.
Bevan

Ayrıca, birden fazla delegenin abone olduğu bir olayı çağırmanın, tek bir delegeden çok daha pahalı olduğunu gösteren kıyaslamalar da gördüm.
Greg D


2

Ian Griffiths'in bu makalesi, kullanmamanız gereken düzgün numaralar olduğu sonucuna vardığı soruna iki farklı çözüm sunuyor.


3
Ve bu makalenin yazarı olarak konuşursak, şunu eklemem gerekir ki, şimdi C # 6 bunu kutunun dışında boş koşullu operatörlerle (?. Ve. []) Çözüyor.
Ian Griffiths

2

Önerildiği gibi sertifika uzatma yöntemi, yarış koşullarıyla ilgili sorunları gerçekten çözmez, aksine onları gizler.

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

Belirtildiği gibi bu kod, geçici değişkenli çözüme mükemmel bir eşdeğerdir, ancak ...

Her ikisindeki sorun , etkinliğin abonesinin etkinlikten çıktıktan SONRA çağrılabilmesidir . Bu mümkündür, çünkü abonelik iptali temsilci örneği temp değişkenine kopyalandıktan sonra (veya yukarıdaki yöntemde parametre olarak iletildikten sonra), ancak temsilci çağrılmadan önce gerçekleşebilir.

Genel olarak, böyle bir durumda istemci kodunun davranışı tahmin edilemez: bileşen durumu, olay bildirimini halihazırda işlemeye izin veremezdi. Müşteri kodunu işleyebilecek şekilde yazmak mümkündür, ancak müşteriye gereksiz sorumluluk yükler.

İş parçacığı güvenliğini sağlamanın bilinen tek yolu, olayı gönderen için kilit ifadesini kullanmaktır. Bu, tüm aboneliklerin \ abonelikten çıkma \ çağrıların serileştirilmesini sağlar.

Daha doğru olmak için kilit, varsayılan 'this' olan add \ remove olay erişimci yöntemlerinde kullanılan aynı senkronizasyon nesnesine uygulanmalıdır.


Bu, atıfta bulunulan yarış durumu değildir. Yarış koşulu, if (MyEvent! = Null) // MyEvent artık boş değil MyEvent.Invoke (); // MyEvent artık boş, kötü şeyler oluyor Bu yüzden bu yarış koşuluyla, çalışacağı garantili bir zaman uyumsuz olay işleyicisi yazamıyorum. Bununla birlikte, 'yarış durumunuz' ile, çalışacağı garantili bir zaman uyumsuz olay işleyicisi yazabilirim. Elbette abone ve abonelikten çıkana yapılan aramaların eşzamanlı olması gerekir veya burada kilit kodu gereklidir.
jyoung

Aslında. Bu problemle ilgili analizim
Eric Lippert


1

Belki daha iyi değil ama bence daha okunaklı bir uzatma yöntemi oluşturmaktır

public static bool IsNull(this object obj) {
 return obj == null;
}

1
ne anlama return obj == nullgeliyor. Ne geri dönecek
Hammad Khan

1
O eğer anlamına gelir objolduğunu nullyöntem dönecektir truediye düşünüyorum.
Joel

1
Bu soruya nasıl cevap veriyor?
Kugel

Geç cevap, ama bunun işe yarayacağına emin misin? Bir nesnede bir uzantı yöntemi çağırabilir misiniz null? Bunun türün kendi yöntemleriyle çalışmadığından oldukça eminim, bu yüzden uzatma yöntemleriyle de çalışacağından şüpheliyim. Ben bir nesne anlamanın en iyi yolu kontrol etmek inanıyoruz olduğunu null olduğunu obj is null. Bir amacı, ne yazık ki, kontrol etmek için değil değildir nulltalihsiz parantez içinde sarma gerektirir.
natiiix

0

Kullandığım bu genel uzantıyı yaptım.

public static class ObjectExtensions {
    public static void With<T>(this T value, Action<T> todo) {
        if (value != null) todo(value);
    }
}

O zaman aşağıdaki gibi kullanıyorum.

string myString = null;
myString.With((value) => Console.WriteLine(value)); // writes nothing
myString = "my value";
myString.With((value) => Console.WriteLine(value)); // Writes `my value`

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.