Property-Coalescing-Operator für C #


9

Mit dem Null-Koaleszenz-Operator in c # können Sie den Code verkürzen

  if (_mywidget == null)
     return new Widget();
  else
     return _mywidget;

Bis zu:

  return _mywidget ?? new Widget();

Ich stelle immer wieder fest, dass ein nützlicher Operator, den ich in C # haben möchte, einer ist, mit dem Sie eine Eigenschaft eines Objekts oder einen anderen Wert zurückgeben können, wenn das Objekt null ist. Also möchte ich ersetzen

  if (_mywidget == null)
     return 5;
  else
     return _mywidget.Length;

Mit:

  return _mywidget.Length ??! 5;

Ich kann mir nicht helfen zu denken, dass es einen Grund dafür geben muss, dass dieser Operator nicht existiert. Ist es ein Code-Geruch? Gibt es einen besseren Weg, dies zu schreiben? (Ich kenne das Nullobjektmuster, aber es scheint übertrieben, es zu verwenden, um diese vier Codezeilen zu ersetzen.)


1
Würde der bedingte Operator hier ausreichen?
Anon.

1
Jemand hat etwas geschrieben, mit dem Sie so etwas tun können: string location = employee.office.address.location ?? "Unbekannt"; . Dadurch wird der Standort auf "Unbekannt" gesetzt, wenn eines der Objekte (entweder Mitarbeiter , Büro , Adresse oder Standort ) null ist. Leider kann ich mich nicht erinnern, wer es geschrieben hat oder wo er es gepostet hat. Wenn ich es wieder finde, werde ich es hier posten!
Kristof Claes

1
Diese Frage würde bei StackOverflow viel mehr Anklang finden.
Job

2
??!ist ein Operator in C ++. :-)
James McNellis

3
Hallo 2011, habe gerade angerufen, um zu sagen, dass c # einen sicheren Navigationsbetreiber
Nathan Cooper

Antworten:


5

Ich möchte unbedingt eine C # -Sprachenfunktion, mit der ich x.Prop.SomeOtherProp.ThirdProp sicher ausführen kann, ohne jede dieser Funktionen auf Null prüfen zu müssen. In einer Codebasis, in der ich häufig solche "Deep Property Traversals" durchführen musste, schrieb ich eine Erweiterungsmethode namens Navigate, die NullReferenceExceptions verschluckte und stattdessen einen Standardwert zurückgab. Also könnte ich so etwas machen:

var propVal = myThing.Navigate(x => x.PropOne.PropTwo.PropThree.PropFour, defaultValue);

Der Nachteil dabei ist, dass es einen anderen Codegeruch hat: verschluckte Ausnahmen. Wenn Sie so etwas wie "richtig" machen möchten, können Sie die Lamdba als Ausdruck verwenden und den Ausdruck im laufenden Betrieb ändern, um die Nullprüfungen für jeden Eigenschaftszugriff hinzuzufügen. Ich habe mich für "schnell und schmutzig" entschieden und es nicht "besser" umgesetzt. Aber wenn ich ein paar freie Gedankenzyklen habe, werde ich das vielleicht noch einmal überdenken und irgendwo posten.

Um Ihre Frage direkter zu beantworten, schätze ich, dass dies kein Feature ist, weil die Kosten für die Implementierung des Features den Nutzen des Features übersteigen. Das C # -Team muss auswählen, auf welche Funktionen es sich konzentrieren soll, und diese ist noch nicht ganz oben angekommen. Nur eine Vermutung, obwohl ich keine Insider-Informationen habe.


1
Genau das, was ich dachte, aber die Leistung des sicheren Weges wäre ziemlich schlecht, denke ich (wegen der vielen Expression.Compile ()) ... Schade, dass es nicht in C # implementiert ist (so etwas wie Obj.?PropA.?Prop1)
Guillaume86

x.PropOne.PropTwo.PropThree.PropFour ist eine schlechte Wahl für das Design, da es gegen das Demeter-Gesetz verstößt. Gestalten Sie Ihre Methoden / Klassen neu, damit Sie diese nicht verwenden müssen.
Justin Shield

Das sinnlose Vermeiden eines tiefen Zugriffs auf Eigentum im Lichte des Demeter-Gesetzes ist ebenso gefährlich wie das sinnlose Normalisieren einer Datenbank. Du kannst zu weit gehen.
Mir

3
In C # 6.0 ist dies möglich, indem der Null-Bedingte Operator (auch bekannt als Elvis-Operator) msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan

15

Ich glaube, Sie können dies jetzt ganz einfach mit C # 6 tun:

return _mywidget?.Length ?? 5;

Beachten Sie den nullbedingten Operator ?.auf der linken Seite. _mywidget?.LengthGibt null zurück, wenn _mywidgetnull ist.

Ein einfacher ternärer Operator ist jedoch möglicherweise leichter zu lesen, wie andere vorgeschlagen haben.


9

Es ist wirklich nur eine Spekulation darüber, warum sie es nicht getan haben. Immerhin glaube ich nicht? war in der ersten C # -Version.

Wie auch immer, ich würde einfach den bedingten Operator verwenden und ihn einen Tag nennen:

return (_mywidget != null) ? _mywidget.Length : 5;

Das ?? ist nur eine Verknüpfung zum bedingten Operator.


5
Persönlich ist dies genauso schlecht (imho) wie ein Bediener, der dies überhaupt tut. Sie möchten etwas von einem Objekt ... dieses Objekt ist möglicherweise nicht vorhanden ... also geben wir 5als Antwort an, falls es nicht vorhanden ist. Sie müssen sich Ihr Design ansehen, bevor Sie nach speziellen Operatoren fragen.
Moo-Juice

1
@ Moo-Juice Ich stelle mir nur die Person vor, die diesen Code verwaltet: 'Warum gibt mir das 5? Hrmmm. Was?!'
Msarchet

4

Es sieht genauso aus wie der ternäre Operator:

return (_mywidget != NULL) ? _mywidget : new Widget();

3

Persönlich denke ich, dass es an der Lesbarkeit liegt. Im ersten Beispiel bedeutet das ??ganz gut "Bist du da? Nein, lass uns das machen".

Im zweiten Beispiel ??!ist es eindeutig WTF und bedeutet dem Programmierer nichts .... das Objekt existiert nicht, daher kann ich nicht an die Eigenschaft gelangen, also werde ich 5 zurückgeben. Was bedeutet das überhaupt? Was ist 5? Wie kommen wir zu dem Schluss, dass 5 ein guter Wert ist?

Kurz gesagt, das erste Beispiel macht Sinn ... nicht da, neu es. Im zweiten ist es offen für Debatten.


2
Nun, Sie müssen die Tatsache ignorieren, dass ??! existiert nicht. Stellen Sie sich vor, wenn ??! war üblich, und wenn es verwendet wurde, dann treffen Sie Entscheidungen auf dieser Grundlage.
Whatsisname

3
Das erinnert mich an den sogenannten "WTF-Operator" in C ++ (das funktioniert tatsächlich : (foo() != ERROR)??!??! cerr << "Error occurred" << endl;.)
Tamás Szelei

@whatsisname, es war eher eine Reflexion über die Tatsache, dass es für die getroffene Entscheidung angemessen war. Ein Objekt zu erstellen, weil es nicht da war, macht Sinn. Es ist in der Tat ein WTF-Szenario , einen Arbituary-Wert zuzuweisen, der dem Leser nichts bedeutet, weil Sie nicht an eine Eigenschaft von etwas gelangen können .
Moo-Juice

Ich denke, Sie werden in mein Beispiel verwickelt. Vielleicht ist 0 besser als 5. Vielleicht ist die Eigenschaft ein Objekt. Wenn also _mywidget null ist, möchten Sie ein neues foodle () zurückgeben. Ich stimme dem überhaupt nicht zu? ist lesbarer als ??! obwohl :)
Ben Fulton

@Ben, Obwohl ich damit einverstanden bin, dass die Konzentration auf den Operator selbst Ihrer Frage nicht allzu viel verleiht, habe ich eine Parallele zur Wahrnehmung des Programmierers und dieser willkürlichen gezogen 5. Ich denke wirklich, dass es eher ein Problem gibt, dass Sie so etwas tun müssen, während in Ihrem ersten Beispiel die Notwendigkeit ziemlich offensichtlich ist, insbesondere bei Dingen wie Cache-Managern.
Moo-Juice

2

Ich denke, die Leute sind zu sehr in die "5" verwickelt, als dass Sie zurückkehren ... vielleicht wäre 0 besser gewesen :)

Wie auch immer, ich denke das Problem ist, dass ??!es sich nicht um einen eigenständigen Operator handelt. Überlegen Sie, was dies bedeuten würde:

var s = myString ??! "";

In diesem Fall macht es keinen Sinn: Es macht nur Sinn, wenn der linke Operand ein Eigenschaftszugriff ist. Oder was ist damit:

var s = Foo(myWidget.Length) ??! 0;

Es gibt eine Eigenschaftenaccessor da drin, aber ich glaube immer noch nicht , es Sinn macht (wenn myWidgetist null, heißt das nennen wir nicht Foo()überhaupt?), Oder ist das nur ein Fehler?

Ich denke, das Problem ist, dass es einfach nicht so natürlich in die Sprache ??passt wie es ist.


1

Jemand hatte eine Utility-Klasse erstellt, die dies für Sie erledigt. Aber ich kann es nicht finden. Was ich gefunden habe, war etwas Ähnliches in den MSDN-Foren (siehe die zweite Antwort).

Mit ein wenig Arbeit können Sie es erweitern, um Methodenaufrufe und andere Ausdrücke auszuwerten, die das Beispiel nicht unterstützt. Sie können es auch erweitern, um einen Standardwert zu akzeptieren.


1

Ich verstehe, woher du kommst. Es scheint, als würden sich alle mit dem von Ihnen angegebenen Beispiel um die Achse wickeln. Ich stimme zwar zu, dass magische Zahlen eine schlechte Idee sind, aber für bestimmte Eigenschaften würde das Äquivalent eines Null-Coallescing-Operators verwendet.

Sie haben beispielsweise Objekte an eine Karte gebunden und möchten, dass sie sich auf der richtigen Höhe befinden. DTED-Karten können Löcher in ihren Daten haben, so dass es möglich ist, einen Nullwert sowie Werte im Bereich von etwa -100 bis ~ 8900 Metern zu haben. Vielleicht möchten Sie etwas in der Art von:

mapObject.Altitude = mapObject.Coordinates.DtedAltitude ?? DEFAULT_ALTITUDE;

Der Null-Coallescing-Operator würde in diesem Fall die Standardhöhe auffüllen, wenn die Koordinaten noch nicht festgelegt wären oder das Coordinates-Objekt keine DTED-Daten für diesen Speicherort laden könnte.

Ich sehe dies als sehr wertvoll an, aber meine Spekulationen darüber, warum es nicht gemacht wurde, beschränken sich auf die Komplexität des Compilers. Es kann einige Fälle geben, in denen das Verhalten nicht so vorhersehbar ist.


1

Wenn ich mich nicht mit tief verschachtelten Operatoren befasse, habe ich dies verwendet (vor dem in C # 6 eingeführten Null-Koaleszenz-Operator). Für mich ist es ziemlich klar, aber vielleicht liegt das daran, dass ich daran gewöhnt bin, andere Leute finden es vielleicht verwirrend.

return ( _mywidget ?? new MyWidget() {length = defaultLength}).Length;

Dies ist natürlich nicht immer der Fall, da die Eigenschaft, auf die Sie zugreifen müssen, manchmal nicht direkt festgelegt werden kann oder wenn die Konstruktion des Objekts selbst unter anderem teuer ist.

Eine andere Alternative ist die Verwendung des NullObject-Musters . Sie definieren eine einzelne statische Instanz der Klasse mit sinnvollen Standardwerten und verwenden diese stattdessen.

return ( _mywidget ?? myWidget.NullInstance).Length;

0

Magische Zahlen sind normalerweise ein schlechter Geruch. Wenn die 5 eine beliebige Zahl ist, ist es besser, sie so zu formulieren, dass sie klarer dokumentiert ist. Eine Ausnahme wäre meiner Meinung nach, wenn Sie eine Sammlung von Werten haben, die in einem bestimmten Kontext als Standardwerte fungieren.

Wenn Sie eine Reihe von Standardwerten für ein Objekt haben, können Sie diese in Unterklassen unterteilen, um eine Standard-Singleton-Instanz zu erstellen.

So etwas wie: return (myobj ?? default_obj) .Length


0

Die Length-Eigenschaft ist normalerweise ein Werttyp, daher kann sie nicht null sein, sodass der Null-Koaleszenz-Operator hier keinen Sinn ergibt.

Sie können dies jedoch mit einer Eigenschaft tun, die ein Referenztyp ist, zum Beispiel: var window = App.Current.MainWindow ?? new Window();


Das Beispiel versucht zu überlegen, was in Ihrer Situation passieren würde, wenn Current null wäre. Ihr Beispiel würde einfach abstürzen ... aber es wäre praktisch, einen Operator zu haben, der überall in der Fehlleitungskette null verschmelzen kann.
Mir

-1

Ich habe schon jemanden mit einer ähnlichen Idee gesehen, aber meistens möchten Sie den neuen Wert auch dem Nullfeld zuweisen, etwa:

return _obj ?? (_obj = new Class());

Die Idee war also, das ??und die =Zuordnung zu kombinieren in:

return _obj ??= new Class();

Ich denke, das ist sinnvoller als die Verwendung eines Ausrufezeichens.

Diese Idee ist nicht meine, aber ich mag sie wirklich :)


1
Bist du ein Opfer des schlechten Beispiels? Ihr Beispiel kann so gekürzt werden, return _obj ?? new Class();dass hier keine Notwendigkeit besteht ??=.
Matt Ellen

@ Matt Ellen, du hast es falsch verstanden. Die Zuordnung ist vorgesehen . Ich schlage eine andere Alternative vor. Es ist nicht ganz genau so, wie es das OP verlangt hat, aber ich glaube, es wird genauso nützlich sein.
Chakrit

2
Warum möchten Sie die Zuweisung, wenn Sie einen Wert zurückgeben möchten?
Matt Ellen

Lazy-Initialisierung?
Chakrit
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.