Какой самый элегантный способ написать метод «Try» в C # 7?


21

Я пишу тип реализации Queue, в котором есть TryDequeueметод, который использует шаблон, аналогичный различным TryParseметодам .NET , где я возвращаю логическое значение, если действие выполнено успешно, и использую outпараметр для возврата фактического значения в очереди.

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

Теперь я люблю избегать outпараметров, когда могу. C # 7 выдает нам переменные делкарации, чтобы упростить работу с ними, но я все же считаю параметры скорее необходимым злом, чем полезным инструментом.

Поведение, которое я хочу от этого метода, следующее:

  • Если есть предмет для удаления из очереди, верните его.
  • Если нет элементов для удаления из очереди (очередь пуста), предоставьте вызывающей стороне достаточно информации, чтобы действовать соответствующим образом.
  • Не просто возвращайте нулевой элемент, если ничего не осталось.
  • Не выбрасывайте исключение, если пытаетесь удалить из пустой очереди.

Прямо сейчас вызывающая сторона этого метода почти всегда использует шаблон, подобный следующему (используя синтаксис переменной C # 7 out):

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

Что не самое худшее, все сказали. Но я не могу помочь, но чувствую, что могут быть более хорошие способы сделать это (я полностью готов признать, что не может быть). Я ненавижу, как вызывающий должен разбить свой поток на условные, когда все, что он хочет, это получить значение, если оно существует.

Какие существуют другие паттерны, способствующие такому же «пробному» поведению?

Для контекста этот метод потенциально может быть вызван в проектах VB, так что бонусные баллы за то, что хорошо работает в обоих. Этот факт должен иметь очень небольшой вес, хотя.


9
Определите Option<T>структуру и верните ее. Эти bool Try(..., out data)функции - мерзость.
CodesInChaos

2
Я думал аналогично ... Может быть, <T>, вариант <T>, если один из них неблагоприятен для параметров OUT.
Джон Рейнор

1
@FrustratedWithFormsDesigner хорошо, я не столько "пробовал" другие вещи (каламбуры приветствуются), сколько думал о них. У меня была идея вернуть ValueTuple, но в лучшем случае я не думаю, что это принесло бы много улучшений.
Эрик Сондергард,

@CodesInChaos Мы на одной странице, поэтому я здесь! И мне нравится эта идея. Если у вас есть время, не могли бы вы дать более подробную информацию в ответе, чтобы я мог его принять?
Эрик Сондергард,

Ответы:


24

Используйте тип Option, то есть объект типа, который имеет две версии, обычно называемые «Some» (при наличии значения) или «None» (при отсутствии значения) ... Или иногда они называются справедливо и ничего. Затем в этих типах есть функции, которые позволяют получить доступ к значению, если оно присутствует, проверить наличие, и, что наиболее важно, метод, который позволяет применить функцию, которая возвращает дополнительный параметр к значению, если оно присутствует (обычно в C # это должно быть названным FlatMap, хотя в других языках это часто называют Bind вместо ... Имя является критическим в C #, потому что наличие метода s этого имени и типа позволяет вам использовать ваши объекты Option в операторах LINQ).

Дополнительные функции могут включать в себя такие методы, как IfPresent и IfNotPresent для вызова действий в соответствующих условиях и OrElse (который заменяет значение по умолчанию, когда значение отсутствует, но в противном случае это не операция), и так далее.

Ваш пример может выглядеть примерно так:

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

Это образец монады Option (или Maybe), и он чрезвычайно полезен. Существуют реализации (например, https://github.com/nlkl/Optional/blob/master/README.md ), но это не так сложно сделать самостоятельно.

(Возможно, вы захотите расширить этот шаблон, чтобы вы возвращали описание причины ошибки, а не ничего, когда метод завершается ошибкой ... Это вполне достижимо и часто называется монадой Either; как следует из названия, вы можете использовать ту же FlatMap шаблон, чтобы облегчить работу в этом случае тоже)


Это, безусловно, хороший вариант, спасибо за предложение и ваши мысли. Я действительно искал больше идей здесь.
Эрик Сондергард,

2
Приятно то, что Option/ Maybeэто то, что это монада (если она, конечно, реализована как единое целое), что означает, что она может быть надежно соединена, обернута, развернута и обработана без необходимости обрабатывать различные случаи напрямую, и эта цепочка и обработка может быть выполнена с помощью выражений запросов LINQ.
Йорг Миттаг,

3
Кстати, flatMap/ bindназывается SelectManyв .NET.
Йорг Миттаг,

5
Интересно, было бы лучше выбрать другое имя, так как этим вы нарушаете Try...соглашения об именах в .NET (и, возможно, сбивает с толку сопровождающих).
Боб

@ Боб Это замечательный момент. Я согласен.
Эрик Сондергард

12

Данные ответы хороши, и я бы пошел с одним из них. Считайте, что этот ответ просто заполняет несколько углов альтернативными идеями:

  • Сделайте подклассы Messageвызываемых SomeMessageи NoMessage. Dequeueтеперь может вернуться, NoMessageесли нет сообщения, и SomeMessageесли есть сообщение. Если вызываемый абонент хочет определить, в каком случае он находится, он может легко сделать это путем проверки типа. Если они этого не делают, просто делают NoMessageничего не делать, когда вызывается какой-либо из его методов, и они получают то, что просили.

  • То же, что и выше, но сделать Messageнеявно конвертируемым bool(или реализовать operator true / operator false). Теперь вы можете сказать, if (message)чтобы оно было правдой, если сообщение хорошее, и ложным, если оно плохое. (Это почти наверняка плохая идея, но я включил ее для полноты!)

  • C # 7 имеет кортежи. Верните (bool, Message)кортеж.

  • Сделайте Messageтип структуры и верните Message?.


Эй, спасибо за твой ответ. С этим вопросом я так же старался разобраться в разных идеях, как и пытался найти решение своей конкретной проблемы. Ура!
Эрик Сондергард,

Я имею в виду, если Unity использует вашу «плохую идею», почему мы не можем ее использовать?
Артуро Торрес Санчес

@ ArturoTorresSánchez: Ваш вопрос «кто-то другой использовал очень плохую практику программирования, так почему я не могу?» Вопрос бессвязный. Никто не мешает вам использовать те же самые плохие методы программирования, что и другие. Вы идете прямо вперед. Это не сделает это хорошей практикой в ​​C #.
Эрик Липперт

Это был язык в щеке. Я думаю, мне нужно использовать "/ s", чтобы пометить его.
Артуро Торрес Санчес

@ ArturoTorresSánchez: это сайт с вопросами и ответами; Я считаю вопросы вопросами , которые ищут ответы .
Эрик Липперт

10

В C # 7 вы можете использовать сопоставление с образцом, чтобы добиться того же более элегантным способом:

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

В этом конкретном случае, однако, я бы, вероятно, использовал события вместо того, чтобы повторно опрашивать очередь.


3
Разве это не требует TryDequeueвозврата некоторого интерфейса маркера с Messageреализацией и некоторой реализацией «ничего»? Конечно, это более элегантно на call-сайте, но вы застряли в реализации 3-х реализаций в реальном коде, что само по себе может оказаться невозможным, если Messageэто существующий тип.
Теластин

1
@Telastyn: это также работает, если это только возвращает нуль . Вы также можете использовать тип Option.
JacquesB

@JacquesB: но что, если null является допустимым элементом очереди?
JBSnorro

5

Нет ничего плохого в методе Try ... (с параметром out) для сценария, в котором сбой (без значения) так же распространен, как и успех.

Но если вы настаиваете на том, чтобы делать что-то по-другому, вы можете захотеть вернуть пустое сообщение и либо просто отправить его (больше не ваша проблема), либо отложить утверждение if, пока вы действительно не узнаете, есть ли у вас сообщение с контентом или нет.

Обратите внимание, что кто-то должен пройти тест в любом случае. Я бы сказал, что тест должен быть сделан, когда и где этот вопрос актуален. Это в момент оформления.


Ты делаешь доброе дело. Вам все еще нужно проверить, что бы вы ни делали. Честно говоря, я все еще могу идти с параметрами в данный момент (на самом деле я сейчас выставляю несколько реализаций TryDequeue, чтобы просто поиграть с каждой из них). Я действительно просто хотел обсудить другие варианты, которые могут быть там.
Эрик Сондергард
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.