Operator koalescencji właściwości dla C #

9

Operator zerowego koalescencji w c # pozwala skrócić kod

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

Aż do:

  return _mywidget ?? new Widget();

Ciągle odkrywam, że użytecznym operatorem, który chciałbym mieć w języku C #, byłby taki, który pozwoliłby ci zwrócić właściwość obiektu lub inną wartość, jeśli obiekt jest pusty. Więc chciałbym wymienić

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

Z:

  return _mywidget.Length ??! 5;

Nie mogę przestać myśleć, że musi istnieć jakiś powód, dla którego ten operator nie istnieje. Czy to zapach kodu? Czy jest jakiś lepszy sposób na napisanie tego? (Zdaję sobie sprawę z wzorca zerowego obiektu, ale wydaje się, że używanie go do zastąpienia tych czterech wierszy kodu jest przesadą).

Ben Fulton
źródło
1
Czy wystarczy operator warunkowy?
Anon.
1
Ktoś napisał coś, co umożliwia zrobienie czegoś takiego: string location = pracownik.office.address.location ?? "Nieznany"; . Spowoduje to ustawienie lokalizacji na „Nieznany”, gdy jeden z obiektów ( pracownik , biuro , adres lub lokalizacja ) jest pusty. Niestety nie pamiętam, kto go napisał ani gdzie go opublikował. Jeśli go znajdę, opublikuję go tutaj!
Kristof Claes,
1
To pytanie zyskałoby znacznie większą przyczepność na StackOverflow.
Job
2
??!jest operatorem w C ++. :-)
James McNellis
3
Witaj 2011, właśnie zadzwoniłem, aby powiedzieć, że c # dostaje bezpiecznego operatora nawigacji
Nathan Cooper

Odpowiedzi:

5

Bardzo chcę funkcji języka C #, która pozwala mi bezpiecznie wykonywać x.Prop.SomeOtherProp.ThirdProp bez konieczności sprawdzania każdego z nich pod kątem wartości null. W jednej bazie kodu, w której musiałem często wykonywać tego rodzaju „głębokie przejścia własności”, napisałem metodę rozszerzenia o nazwie Navigate, która połknęła NullReferenceExceptions i zamiast tego zwróciła wartość domyślną. Więc mógłbym zrobić coś takiego:

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

Minusem tego jest to, że ma inny zapach kodu: połknięte wyjątki. Jeśli chcesz zrobić coś takiego „dobrze”, możesz wziąć lamdba jako wyrażenie i zmodyfikować wyrażenie w locie, aby dodać kontrole zerowe wokół każdego akcesorium właściwości. Wybrałem „szybki i brudny” i nie wdrożyłem go w ten „lepszy” sposób. Ale może kiedy będę miał zapasowe cykle myślowe, wrócę do tego i gdzieś opublikuję.

Aby bardziej bezpośrednio odpowiedzieć na twoje pytanie, domyślam się, że powodem, dla którego nie jest to funkcja, jest to, że koszt wdrożenia tej funkcji przewyższa korzyść z jej posiadania. Zespół C # musi wybrać, na których funkcjach się skoncentrować, a ta jeszcze nie dotarła na szczyt. Tylko zgaduję, że nie mam żadnych informacji poufnych.

RationalGeek
źródło
1
Dokładnie tak, jak myślałem, ale wydajność bezpiecznego sposobu byłaby dość zła (chyba z powodu wielu Expression.Compile ()) ... Szkoda, że ​​nie jest zaimplementowany w języku C # (coś takiego jak Obj.?PropA.?Prop1)
Guillaume86
x.PropOne.PropTwo.PropThree.PropFour jest złym wyborem projektowym, ponieważ narusza prawo Demetera. Przeprojektuj swoje metody / klasy, abyś nie musiał z nich korzystać.
Justin Shield
Bezmyślne unikanie głębokiego dostępu do nieruchomości w świetle Prawa Demetera jest równie niebezpieczne, jak bezmyślna normalizacja bazy danych. Możesz posunąć się za daleko.
Mir
3
W C # 6.0 jest to możliwe przy użyciu Operatora Null-Conditional (zwanego także operatorem Elvisa) msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan
15

Wierzę, że będziesz w stanie to zrobić z C # 6 po prostu teraz:

return _mywidget?.Length ?? 5;

Zwróć uwagę na zerowy operator warunkowy ?.po lewej stronie. _mywidget?.Lengthzwraca null, jeśli _mywidgetjest null.

Prosty operator trójskładnikowy może być łatwiejszy do odczytania, jak sugerowali inni.

Chris Nolet
źródło
9

To tylko spekulacje, dlaczego tego nie zrobili. W końcu nie wierzę? był w pierwszej wersji C #.

W każdym razie po prostu użyłbym operatora warunkowego i nazwałbym go dniem:

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

?? jest tylko skrótem do operatora warunkowego.

Jaka jest nazwa?
źródło
5
Osobiście jest to tak samo złe (imho) jak posiadanie operatora, który to robi. Chcesz czegoś od obiektu ... ten obiekt może nie być tam ... więc podajmy 5odpowiedź na wypadek, gdyby go nie było. Zanim zaczniesz prosić o specjalnych operatorów, musisz spojrzeć na swój projekt.
Moo-Juice
1
@ Moo-Juice Wyobrażam sobie tylko osobę, która utrzymuje ten kod: „Dlaczego daje mi to 5? Hmmmm. Co?!'
msarchet
4

Wygląda jak operator trójskładnikowy:

return (_mywidget != NULL) ? _mywidget : new Widget();
Martin York
źródło
3

Osobiście uważam, że zależy to od czytelności. W pierwszym przykładzie ??tłumaczy się całkiem ładnie: „Jesteś tam? Nie, zróbmy to”.

W drugim przykładzie ??!jest wyraźnie WTF i nic nie znaczy dla programisty ... obiekt nie istnieje, więc nie mogę dostać się do właściwości, więc zwrócę 5. Co to w ogóle oznacza? Co to jest 5? Jak dojść do wniosku, że 5 to dobra wartość?

Krótko mówiąc, pierwszy przykład ma sens ... nie ma go, nowy . W drugim przypadku jest otwarta do debaty.

Sok Moo
źródło
2
Cóż, musisz zignorować fakt, że ??! nie istnieje. Wyobraź sobie, że ?? był powszechny, a jeśli był używany, podejmuj na nim decyzje.
whatsisname
3
To przypomina mi tak zwany „operator WTF” w C ++ (to faktycznie działa (foo() != ERROR)??!??! cerr << "Error occurred" << endl;
:.
@whatsisname, było to bardziej refleksja na temat tego, że było to właściwe dla podejmowanej decyzji. Stworzenie obiektu, ponieważ go tam nie było, ma sens. Przypisanie wartości arbitralnej, która nic nie znaczy dla czytelnika, ponieważ nie można uzyskać właściwości czegoś, jest rzeczywiście scenariuszem WTF.
Moo-Juice
Myślę, że przykładasz się do mojego przykładu. Może 0 jest lepsze niż 5. Może właściwość jest obiektem, więc jeśli _mywidget ma wartość null, chcesz zwrócić nową foodle (). Całkowicie się z tym nie zgadzam? jest bardziej czytelny niż ??! chociaż :)
Ben Fulton
@Ben, Chociaż zgadzam się, że skupienie się na samym operatorze nie poddaje zbytnio waszego pytania, ja jednocześnie zbliżam się do postrzegania programisty i tego arbitralnego 5. Naprawdę uważam, że jest więcej problemów, które musisz zrobić w ten sposób, podczas gdy w twoim pierwszym przykładzie potrzeba jest dość oczywista, szczególnie w takich rzeczach, jak menedżerowie pamięci podręcznej.
Moo-Juice
2

Myślę, że ludzie są zbyt pochłonięci „5”, którą powracasz ... być może 0 byłoby lepiej :)

W każdym razie myślę, że problem polega na tym, że ??!tak naprawdę nie jest to samodzielny operator. Zastanów się, co to znaczy:

var s = myString ??! "";

W takim przypadku nie ma to sensu: ma sens tylko wtedy, gdy lewy operand jest akcesorium własności. A co z tym:

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

Jest tam akcesorium do nieruchomości, ale nadal nie sądzę, aby miało to sens (jeśli myWidgettak null, czy to znaczy, że w ogóle nie dzwonimy Foo()?), Czy to tylko błąd?

Myślę, że problem polega na tym, że po prostu nie pasuje tak naturalnie do języka, jak ??.

Dean Harding
źródło
1

Ktoś stworzył klasę narzędzi, która zrobiłaby to za ciebie. Ale nie mogę tego znaleźć. Znalazłem coś podobnego na forach MSDN (spójrz na drugą odpowiedź).

Przy odrobinie pracy możesz go rozszerzyć, aby ocenić wywołania metod i inne wyrażenia, których próbka nie obsługuje. Można go również rozszerzyć, aby zaakceptować wartość domyślną.

Michael Brown
źródło
1

Rozumiem skąd pochodzisz. Wygląda na to, że wszyscy są owinięci wokół osi zgodnie z podanym przykładem. Chociaż zgadzam się, że liczby magiczne są złym pomysłem, dla niektórych właściwości można by użyć odpowiednika zerowego operatora koalicyzacji.

Na przykład masz obiekty związane z mapą i chcesz, aby były na właściwej wysokości. Mapy DTED mogą mieć dziury w swoich danych, więc można mieć wartość zerową, a także wartości w zakresie od -100 do ~ 8900 metrów. Możesz chcieć czegoś w stylu:

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

W tym przypadku zerowy operator koalescencji zapełniłby domyślną wysokość, jeśli Współrzędne nie zostały jeszcze ustawione lub obiekt Coordinates nie mógłby załadować danych DTED dla tej lokalizacji.

Uważam to za bardzo cenne, ale moje spekulacje, dlaczego tego nie zrobiono, ograniczają się do złożoności kompilatora. Mogą istnieć przypadki, w których zachowanie nie byłoby tak przewidywalne.

Berin Loritsch
źródło
1

Kiedy nie mam do czynienia z głęboko zagnieżdżonymi, użyłem tego (przed wprowadzeniem zerowego operatora koalescencyjnego w C # 6). Dla mnie to całkiem jasne, ale może dlatego, że jestem do tego przyzwyczajony, inni ludzie mogą uznać to za mylące.

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

Oczywiście nie zawsze ma to zastosowanie, ponieważ czasami właściwość, do której trzeba uzyskać dostęp, nie może być ustawiona bezpośrednio, lub gdy budowa samego obiektu jest kosztowna, między innymi.

Inną alternatywą jest użycie wzorca NullObject . Zdefiniuj pojedynczą instancję statyczną klasy z rozsądnymi wartościami domyślnymi i użyj jej zamiast tego.

return ( _mywidget ?? myWidget.NullInstance).Length;
andyroschy
źródło
0

Magiczne liczby to zwykle nieprzyjemny zapach. Jeśli 5 jest dowolną liczbą, lepiej ją przeliterować, aby była wyraźniej udokumentowana. Moim zdaniem wyjątkiem byłby wyjątek, jeśli masz zbiór wartości, które działają jako wartości domyślne w określonym kontekście.

Jeśli masz zestaw wartości domyślnych dla obiektu, możesz podklasować go, aby utworzyć domyślną instancję singletonu.

Coś w stylu: return (myobj ?? default_obj) .Length

Chris Quenelle
źródło
0

Właściwość length zwykle jest typem wartości, więc nie może być zerowa, więc operator koalescencji zerowej nie ma tutaj sensu.

Ale możesz to zrobić za pomocą właściwości, która jest typem odniesienia, na przykład: var window = App.Current.MainWindow ?? new Window();

peancor
źródło
Przykładem jest próba rozważenia, co by się stało w twojej sytuacji, gdyby prąd miał wartość zero. Twój przykład po prostu się zawiesi ... ale przydatny byłby operator, który może zerować łączenie w dowolnym miejscu w łańcuchu błędnych kierunków.
Mir
-1

Widziałem wcześniej kogoś z podobnym pomysłem, ale przez większość czasu chciałbyś również przypisać nową wartość do pola zerowego, na przykład:

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

więc pomysł polegał na połączeniu ??i =przypisania w:

return _obj ??= new Class();

Myślę, że ma to większy sens niż używanie wykrzyknika.

Ten pomysł nie jest mój, ale bardzo mi się podoba :)

czakryt
źródło
1
Czy jesteś ofiarą biednego przykładu? Twój przykład można skrócić do tego, return _obj ?? new Class();że nie ma takiej potrzeby ??=.
Matt Ellen,
@Matt Ellen, pomyliłeś się. Zadanie jest zamierzone . Sugeruję inną alternatywę. Nie jest to dokładnie to, o co prosił PO, ale wierzę, że będzie równie przydatne.
chakrit
2
dlaczego chcesz przypisania, jeśli masz zamiar zwrócić wartość?
Matt Ellen,
leniwa inicjalizacja?
chakrit