Methodenaufruf, wenn nicht null in C #

106

Ist es möglich, diese Aussage irgendwie zu verkürzen?

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

weil ich das zufällig viel schreibe und es ziemlich nervig wird. Das einzige, woran ich denken kann, ist die Implementierung eines Null-Objekt- Musters, aber das kann ich nicht jedes Mal tun, und es ist sicherlich keine Lösung, um die Syntax zu verkürzen.

Und ähnliches Problem mit Ereignissen, wo

public event Func<string> MyEvent;

und dann aufrufen

if (MyEvent != null)
    MyEvent.Invoke();
Jakub Arnold
quelle
40
Wir haben erwogen, C # 4 einen neuen Operator hinzuzufügen: "obj.?SomeMethod ()" würde bedeuten, "SomeMethod aufzurufen, wenn obj nicht null ist, andernfalls null zurückgeben". Leider passte es nicht in unser Budget und wir haben es nie umgesetzt.
Eric Lippert
@ Eric: Ist dieser Kommentar noch gültig? Ich habe irgendwo gesehen, dass es mit 4.0 verfügbar ist?
CharithJ
@CharithJ: Nein. Es wurde nie implementiert.
Eric Lippert
3
@CharithJ: Mir ist die Existenz des Null-Koaleszenz-Operators bekannt. Es macht nicht das, was Darth will; Er möchte einen Zugriffsoperator für Mitglieder, der auf null gesetzt werden kann. (Übrigens enthält Ihr vorheriger Kommentar eine falsche Charakterisierung des Null-Koaleszenz-Operators. Sie wollten sagen "v = m == null? Y: m. Wert kann geschrieben werden v = m ?? y".)
Eric Lippert
4
Für neuere Leser: C # 6.0 implementiert?., Also gibt x? .Y? .Z? .ToString () null zurück, wenn x, y oder z null sind, oder z.ToString (), wenn keiner von ihnen null ist.
David

Antworten:

162

Ab C # 6 können Sie einfach Folgendes verwenden:

MyEvent?.Invoke();

oder:

obj?.SomeMethod();

Das ?.ist der sich null ausbreitende Operator und bewirkt, dass der Operator .Invoke()kurzgeschlossen wird, wenn der Operand ist null. Auf den Operanden wird nur einmal zugegriffen, sodass kein Risiko für das Problem "Wertänderungen zwischen Prüfung und Aufruf" besteht.

===

Vor C # 6 nein: Es gibt keine null-sichere Magie, mit einer Ausnahme; Erweiterungsmethoden - zum Beispiel:

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

jetzt ist dies gültig:

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

Bei Ereignissen hat dies den Vorteil, dass auch die Race-Bedingung entfernt wird, dh Sie benötigen keine temporäre Variable. Normalerweise benötigen Sie also:

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

aber mit:

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

wir können einfach verwenden:

SomeEvent.SafeInvoke(this); // no race condition, no null risk
Marc Gravell
quelle
1
Ich bin ein kleiner Konflikt darüber. Im Fall eines Aktions- oder Ereignishandlers, der normalerweise unabhängiger ist, ist dies sinnvoll. Ich würde die Kapselung für eine reguläre Methode jedoch nicht unterbrechen. Dies impliziert, dass die Methode in einer separaten, statischen Klasse erstellt wird, und ich denke nicht, dass der Verlust der Kapselung und die Verschlechterung der Lesbarkeit / Code-Organisation insgesamt die geringfügige Verbesserung der lokalen Lesbarkeit wert sind
tvanfosson
@tvanfosson - in der Tat; Aber mein Punkt ist, dass dies der einzige Fall ist, von dem ich weiß, wo es funktionieren wird. Und die Frage selbst wirft das Thema Delegierte / Veranstaltungen auf.
Marc Gravell
Dieser Code generiert irgendwo eine anonyme Methode, die den Stack-Trace einer Ausnahme wirklich durcheinander bringt. Ist es möglich, anonyme Methodennamen anzugeben? ;)
Schwester
Falsch gemäß 2015 / C # 6.0 ...? ist er Lösung. ReferenceTHatMayBeNull? .CallMethod () ruft die Methode nicht auf, wenn sie null ist.
TomTom
1
@mercu sollte es sein ?.- in VB14 und höher
Marc Gravell
27

Was Sie suchen, ist der Operator Null Conditional (nicht "Coalescing") : ?.. Es ist ab C # 6 verfügbar.

Ihr Beispiel wäre obj?.SomeMethod();. Wenn obj null ist, passiert nichts. Wenn die Methode Argumente enthält, werden obj?.SomeMethod(new Foo(), GetBar());die Argumente beispielsweise nicht ausgewertet, wenn sie objnull sind. Dies ist wichtig, wenn die Auswertung der Argumente Nebenwirkungen hätte.

Und Verkettung ist möglich: myObject?.Items?[0]?.DoSomething()

Mumm
quelle
1
Das ist fantastisch. Es ist erwähnenswert, dass dies eine C # 6-Funktion ist ... (Dies würde aus Ihrer VS2015-Anweisung impliziert, ist aber dennoch erwähnenswert). :)
Kyle Goode
10

Eine schnelle Erweiterungsmethode:

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

Beispiel:

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

oder alternativ:

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

Beispiel:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));
Katbyte
quelle
ich hatte ein Versuch, dies mit Erweiterungsmethoden zu tun, und am Ende hatte ich fast den gleichen Code. Das Problem bei dieser Implementierung ist jedoch, dass sie nicht mit Ausdrücken funktioniert, die einen Werttyp zurückgeben. Also musste ich dafür eine zweite Methode haben.
Orad
4

Ereignisse können mit einem leeren Standarddelegaten initialisiert werden, der niemals entfernt wird:

public event EventHandler MyEvent = delegate { };

Keine Nullprüfung erforderlich.

[ Update , danke an Bevan für den Hinweis]

Beachten Sie jedoch die möglichen Auswirkungen auf die Leistung. Ein schneller Mikro-Benchmark, den ich durchgeführt habe, zeigt an, dass die Behandlung eines Ereignisses ohne Abonnenten 2-3-mal langsamer ist, wenn das Muster "Standarddelegierter" verwendet wird. (Auf meinem 2,5-GHz-Dual-Core-Laptop bedeutet dies 279 ms: 785 ms, um 50 Millionen nicht abonnierte Ereignisse auszulösen.) Bei Anwendungs-Hotspots kann dies ein zu berücksichtigendes Problem sein.

Sven Künzler
quelle
1
Sie vermeiden also eine Nullprüfung, indem Sie einen leeren Delegaten aufrufen ... ein messbares Opfer an Speicher und Zeit, um ein paar Tastenanschläge zu sparen? YMMV, aber für mich ein schlechter Kompromiss.
Bevan
Ich habe auch Benchmarks gesehen, die zeigen, dass das Aufrufen eines Ereignisses mit mehreren abonnierten Delegierten erheblich teurer ist als mit nur einem.
Greg D
2

Dieser Artikel von Ian Griffiths gibt zwei verschiedene Lösungen für das Problem, von dem er schlussfolgert, dass es nette Tricks sind, die Sie nicht anwenden sollten.

Darrel Miller
quelle
3
Und als Autor dieses Artikels möchte ich hinzufügen, dass Sie ihn jetzt definitiv nicht verwenden sollten, da C # 6 dies sofort mit den nullbedingten Operatoren (?. Und. []) Löst.
Ian Griffiths
2

Die Cerating Extention-Methode, wie sie vorgeschlagen wurde, löst Probleme mit den Rennbedingungen nicht wirklich, sondern verbirgt sie.

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

Wie bereits erwähnt, ist dieser Code das elegante Äquivalent zur Lösung mit temporärer Variable, aber ...

Das Problem bei beiden ist, dass es möglich ist, dass der Subsciber des Ereignisses aufgerufen wird, nachdem er sich vom Ereignis abgemeldet hat . Dies ist möglich, da die Abmeldung erfolgen kann, nachdem die Delegateninstanz in die temporäre Variable kopiert (oder in der obigen Methode als Parameter übergeben) wurde, jedoch bevor der Delegat aufgerufen wird.

Im Allgemeinen ist das Verhalten des Client-Codes in einem solchen Fall nicht vorhersehbar: Der Komponentenstatus konnte die Ereignisbenachrichtigung nicht bereits verarbeiten. Es ist möglich, Client-Code so zu schreiben, dass er damit umgeht, aber dies würde dem Client unnötige Verantwortung auferlegen.

Die einzige bekannte Möglichkeit, die Thread-Sicherheit zu gewährleisten, ist die Verwendung der Lock-Anweisung für den Absender des Ereignisses. Dadurch wird sichergestellt, dass alle Abonnements \ Abmeldungen \ Aufruf serialisiert werden.

Um genauer zu sein, sollte die Sperre auf dasselbe Synchronisierungsobjekt angewendet werden, das in den Methoden zum Hinzufügen / Entfernen von Ereigniszugriff verwendet wird. Dies ist die Standardeinstellung 'this'.

andrey.tsykunov
quelle
Dies ist nicht die Rennbedingung, auf die Bezug genommen wird. Die Racebedingung ist if (MyEvent! = Null) // MyEvent ist jetzt nicht null MyEvent.Invoke (); // MyEvent ist jetzt null, es passieren schlimme Dinge. Mit dieser Race-Bedingung kann ich keinen asynchronen Event-Handler schreiben, der garantiert funktioniert. Mit Ihrer 'Race Condition' kann ich jedoch einen asynchronen Event-Handler schreiben, der garantiert funktioniert. Natürlich müssen die Anrufe an Teilnehmer und Nicht-Teilnehmer synchron sein, oder dort ist ein Sperrcode erforderlich.
Jyoung
Tatsächlich. Meine Analyse dieses Problems ist hier veröffentlicht: blogs.msdn.com/ericlippert/archive/2009/04/29/…
Eric Lippert
2

Ich stimme der Antwort von Kenny Eliasson zu. Gehen Sie mit Erweiterungsmethoden. Hier finden Sie eine kurze Übersicht über die Erweiterungsmethoden und Ihre erforderliche IfNotNull-Methode.

Erweiterungsmethoden (IfNotNull-Methode)

Rohit
quelle
1

Vielleicht nicht besser, aber meiner Meinung nach besser lesbar ist es, eine Erweiterungsmethode zu erstellen

public static bool IsNull(this object obj) {
 return obj == null;
}
Kenny Eliasson
quelle
1
was bedeutet der return obj == nullMittelwert. Was wird es zurückkehren
Hammad Khan
1
Es bedeutet , dass , wenn objist nulldas Verfahren zurückkehren true, glaube ich.
Joel
1
Wie beantwortet dies überhaupt die Frage?
Kugel
Späte Antwort, aber sind Sie sicher, dass dies überhaupt funktionieren würde? Können Sie eine Erweiterungsmethode für ein Objekt aufrufen null? Ich bin mir ziemlich sicher, dass dies nicht mit den eigenen Methoden des Typs funktioniert, daher bezweifle ich, dass dies auch mit Erweiterungsmethoden funktionieren würde. Ich glaube, der beste Weg, um zu überprüfen, ob ein Objekt ist, null ist obj is null. Um zu überprüfen, ob ein Objekt nicht vorhandennull ist, muss es leider nicht in Klammern gesetzt werden, was unglücklich ist.
Natiiix