Jaki jest najbardziej elegancki sposób napisania metody „Try” w C # 7?

21

Piszę rodzaj implementacji kolejki, która ma TryDequeuemetodę używającą wzorca podobnego do różnych TryParsemetod .NET , w której zwracam wartość logiczną, jeśli akcja się powiodła, i używam outparametru do zwrócenia rzeczywistej wartości kolejkowania.

public bool TryDequeue(out Message message) => _innerQueue.TryDequeue(out message);

Teraz lubię unikać parametrów, outkiedy tylko mogę. C # 7 daje nam zmienne delcarations, aby ułatwić sobie z nimi pracę, ale nadal uważam params za bardziej konieczne zło niż przydatne narzędzie.

Zachowanie, którego chcę od tej metody, jest następujące:

  • Jeśli jest element do usunięcia z pudełka, zwróć go.
  • Jeśli nie ma elementów do usunięcia z kolejki (kolejka jest pusta), przekaż dzwoniącemu wystarczającą ilość informacji, aby działać poprawnie.
  • Nie zwracaj tylko pustego przedmiotu, jeśli nie ma już żadnych przedmiotów.
  • Nie rzucaj wyjątku, jeśli próbujesz usunąć kolejkę z pustej kolejki.

W tej chwili program wywołujący tę metodę prawie zawsze używał wzoru podobnego do następującego (używając składni zmiennej C # 7 out):

if (myMessageQueue.TryDequeue(out Message dequeued))
    MyMessagingClass.SendMessage(dequeued)
else
    Console.WriteLine("No messages!"); // do other stuff

Co nie jest najgorsze. Ale nic nie mogę poradzić, ale czuję, że mogą istnieć lepsze sposoby na to (jestem całkowicie skłonny przyznać, że może nie być). Nienawidzę tego, jak osoba dzwoniąca musi zerwać swój przepływ z warunkiem, gdy wszystko, czego chce, to uzyskać wartość, jeśli taka istnieje.

Jakie są inne wzorce, które pozwalają osiągnąć to samo „próbowanie”?

W kontekście, ta metoda może być potencjalnie wywoływana w projektach VB, więc punkty bonusowe za coś, co działa dobrze w obu. Ten fakt powinien jednak mieć niewielką wagę.

Eric Sondergard
źródło
9
Zdefiniuj Option<T>strukturę i zwróć ją. Te bool Try(..., out data)funkcje są obrzydliwością.
CodesInChaos
2
Myślałem podobnie ... Może <T>, Opcja <T>, jeśli jeden jest niekorzystny dla parametrów OUT.
Jon Raynor
1
@FrustratedWithFormsDesigner dobrze, nie tyle „wypróbowałem” inne rzeczy (kalambury są mile widziane), tyle o nich myślałem. Wpadłem na pomysł, aby zwrócić ValueTuple, ale w najlepszym razie nie sądzę, że to zapewniło wiele ulepszeń.
Eric Sondergard,
@CodesInChaos Jesteśmy na tej samej stronie, dlatego tu jestem! I podoba mi się ten pomysł. Jeśli masz czas, czy chciałbyś podać więcej szczegółów w odpowiedzi, abym mógł je zaakceptować?
Eric Sondergard,

Odpowiedzi:

24

Użyj typu opcji, czyli obiektu typu, który ma dwie wersje, zwykle nazywane „Niektóre” (gdy występuje wartość) lub „Brak” (gdy nie ma wartości) ... Lub czasami nazywają się Just and Nothing. Istnieją następnie funkcje tego typu, które umożliwiają dostęp do wartości, jeśli jest obecna, testowanie obecności, a co najważniejsze, metodę, która pozwala zastosować funkcję, która zwraca kolejną opcję do wartości, jeśli jest obecna (zazwyczaj w języku C # to powinno może być nazywany FlatMap, chociaż w innych językach jest często nazywany Bind ... Nazwa ma krytyczne znaczenie w C #, ponieważ mając metodę s o tej nazwie i typie, możesz używać obiektów Option w instrukcjach LINQ).

Dodatkowe funkcje mogą obejmować metody takie jak IfPresent i IfNotPresent do wywoływania akcji w odpowiednich warunkach oraz OrElse (która zastępuje wartość domyślną, gdy żadna wartość nie jest obecna, ale w przeciwnym razie nie jest możliwa), i tak dalej.

Twój przykład może wtedy wyglądać mniej więcej tak:

myMessageQueue.TryDeque()
    .IfPresent( dequeued => MyMessagingClass.SendMessage(dequeued))
    .IfNotPresent (() =>  Console.WriteLine("No messages!")

Jest to wzorzec monady Option (a może) i jest on niezwykle przydatny. Istnieją już implementacje (np. Https://github.com/nlkl/Optional/blob/master/README.md ), ale nie jest to trudne.

(Możesz rozszerzyć ten wzorzec, aby zwracać opis przyczyny błędu zamiast niczego, gdy metoda zawiedzie ... Jest to w pełni osiągalne i często nazywane jest monadą Either; jak sama nazwa wskazuje, możesz użyć tej samej FlatMap wzór, aby ułatwić także pracę w tym przypadku)

Jules
źródło
To zdecydowanie dobra opcja, dziękuję za sugestię i twoje przemyślenia. Naprawdę chciałem tutaj odkryć więcej pomysłów.
Eric Sondergard
2
Zaletą Option/ Maybejest to, że jest to monada (jeśli jest zaimplementowana jako jedna, oczywiście), co oznacza, że ​​można ją bezpiecznie łączyć, owijać, rozpakowywać i przetwarzać bez konieczności bezpośredniego obsługiwania różnych przypadków, a to łączenie i przetwarzanie można wykonać za pomocą wyrażeń zapytania LINQ.
Jörg W Mittag
3
Nawiasem mówiąc, flatMap/ bindjest wywoływany SelectManyw .NET.
Jörg W Mittag
5
Zastanawiam się, czy lepszym pomysłem byłoby wybranie innej nazwy, ponieważ robiąc to, Try...łamiesz konwencje nazewnictwa w .NET (i potencjalnie mylę opiekunów).
Bob
@ Bob To świetny punkt. Zgadzam się.
Eric Sondergard,
12

Podane odpowiedzi są dobre i wybrałbym jedną z nich. Rozważ tę odpowiedź jako wypełnienie kilku rogów kilkoma alternatywnymi pomysłami:

  • Twórz podklasy Messagenazwanych SomeMessagei NoMessage. Dequeuemoże teraz powrócić, NoMessagejeśli nie ma wiadomości i SomeMessagejeśli jest wiadomość. Jeśli odbiorcy zależy na wykryciu, w jakim są przypadku, mogą to łatwo zrobić poprzez kontrolę typu. Jeśli tego nie zrobią, po prostu NoMessagenie rób nic, gdy wywoływana jest jedna z jej metod, i hej, dostają to, o co prosili.

  • To samo co powyżej, ale Messagedomyślnie zamień na bool(lub zaimplementuj operator true / operator false). Teraz możesz powiedzieć if (message)i mieć rację, jeśli przesłanie jest dobre, a fałsz, jeśli jest złe. (To prawie na pewno zły pomysł, ale uwzględniam go dla kompletności!)

  • C # 7 ma krotki. Zwróć (bool, Message)krotkę.

  • Stwórz Messagetyp struktury i zwróć Message?.

Eric Lippert
źródło
Hej, dzięki za odpowiedź. Z tym pytaniem tak samo starałem się narazić na różne pomysły, jak i próbowałem znaleźć rozwiązanie mojego konkretnego problemu. Twoje zdrowie!
Eric Sondergard,
Chodzi mi o to, że jeśli „zły pomysł” jest używany przez Unity, dlaczego nie możemy go wykorzystać?
Arturo Torres Sánchez
@ ArturoTorresSánchez: Twoje pytanie brzmi: „ktoś inny zastosował naprawdę złą praktykę programistyczną, więc dlaczego nie mogę?” Pytanie jest niespójne. Nikt cię nie powstrzymuje od stosowania takich samych złych praktyk programistycznych jak ktoś inny. Idź prosto. To nie sprawi, że będzie to dobra praktyka w języku C #.
Eric Lippert
To był język w policzek. Chyba muszę zaznaczyć „/ s”.
Arturo Torres Sánchez
@ ArturoTorresSánchez: To jest strona pytań i odpowiedzi; Uważam, że pytania są pytaniami , które szukają odpowiedzi .
Eric Lippert
10

W C # 7 możesz użyć dopasowania wzorca, aby osiągnąć to samo w bardziej elegancki sposób:

if (myMessageQueue.TryDequeue() is Message dequeued) 
{
     MyMessagingClass.SendMessage(dequeued)
} 
else 
{
    Console.WriteLine("No messages!"); // do other stuff
}

Jednak w tym konkretnym przypadku prawdopodobnie użyłbym zdarzeń, zamiast wielokrotnie sondować kolejkę.

JacquesB
źródło
3
Czy nie wymaga TryDequeueto jednak zwrócenia interfejsu interfejsu znacznika z Messageimplementacją i jakiejś implementacji „nic”? Jasne, jest to bardziej eleganckie w witrynie wywołań, ale utkniesz w implementacji 3 implementacji w rzeczywistym kodzie, co samo może być niemożliwe, jeśli Messagejest to istniejący typ.
Telastyn
1
@Telastyn: Działa również, jeśli po prostu zwraca null . Możesz także użyć typu opcji.
JacquesB
@JacquesB: ale co, jeśli null jest prawidłowym elementem w kolejce?
JBSnorro
5

Nie ma nic złego w metodzie Try ... (posiadającej parametr out) dla scenariusza, w którym niepowodzenie (brak wartości) jest tak samo powszechne jak sukces.

Ale jeśli nalegasz, aby postępować inaczej, możesz zwrócić pustą wiadomość i albo po prostu ją wysłać (nie jest to już twój problem), albo odłożyć instrukcję if, aż naprawdę będziesz musiał wiedzieć, czy masz wiadomość z treścią, czy nie.

Pamiętaj, że ktoś i tak musi kiedyś wykonać test. Twierdziłbym, że test należy wykonać, kiedy i gdzie pytanie jest aktualne. To jest w czasie usuwania z pudełka.

Martin Maat
źródło
Masz rację. Nadal musisz testować, bez względu na to, co robisz. Szczerze mówiąc, w tym momencie nadal mogę nie stosować parametrów (w rzeczywistości obecnie ujawniam wiele implementacji „TryDequeue” właśnie po to, aby bawić się z każdym z nich). Naprawdę chciałem tylko omówić inne opcje, które mogą tam być.
Eric Sondergard,