Який найелегантніший спосіб написати метод "Спробуйте" на C # 7?


21

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

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

Тепер я люблю уникати outпарамів, коли можу. C # 7 дає нам змінні декарації, щоб полегшити роботу з ними, але я все-таки вважаю парами більш необхідним злом, ніж корисним інструментом.

Поведінка, яку я хочу від цього методу, полягає в наступному:

  • Якщо є предмет, який потрібно видалити, поверніть його.
  • Якщо немає жодних елементів для видалення (черга порожня), надайте абоненту достатньо інформації, щоб діяти належним чином.
  • Не повертайте нульовий предмет, якщо не залишилося елементів.
  • Не кидайте виняток, якщо намагаєтеся вийти з порожньої черги.

Зараз абонент цього методу майже завжди використовує такий зразок (використовуючи синтаксис змінної C # 7):

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

Використовуйте тип "Опція", тобто об'єкт типу, який має дві версії, як правило, або "Деякі" (коли значення присутнє), або "Немає" (коли немає значення) ... Або періодично їх називають Просто і Ніщо. Потім є функції цих типів, які дозволяють отримати доступ до значення, якщо воно присутнє, перевірити наявність та, головне, метод, який дозволяє застосувати функцію, яка повертає додаткову опцію до значення, якщо воно є (як правило, в C # це повинно називатися FlatMap, хоча в інших мовах його часто називають Bind замість ... Ім'я є критичним у C #, оскільки, маючи метод s цього імені та введіть, давайте ви будете використовувати свої об'єкти Option в операторах LINQ).

Додаткові функції можуть включати такі методи, як IfPresent та IfNotPresent, щоб викликати дії у відповідних умовах, та OrElse (який замінює значення за замовчуванням, коли значення не існує, але іншим є опція) тощо.

Тоді ваш приклад може виглядати приблизно так:

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

Це шаблон монади (або, можливо), і це надзвичайно корисно. Існують існуючі реалізації (наприклад, https://github.com/nlkl/Otional/blob/master/README.md ), але вам також не важко.

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


Це, безумовно, хороший варіант, дякую за пропозицію та ваші думки. Я дуже хотів вивчити більше ідей тут.
Ерік Сондергард

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

3
До речі, flatMap/ bindназивається SelectManyв .NET.
Йорг W Міттаг

5
Цікаво, чи не було б кращою ідеєю вибрати інше ім’я, оскільки, виконуючи це, ви порушуєте умови Try...іменування в .NET (і, можливо, плутаєте обслуговування).
Боб

@Bob Це чудовий момент. Я згоден.
Ерік Сондергард

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реалізацією та деякою реалізацією "нічого"? Звичайно, це більш елегантно на сайті виклику, але ви застрягли в реалізації 3-х реалізацій у фактичному коді, що саме по собі може бути неможливим, якщо Messageце вже існуючий тип.
Теластин

1
@Telastyn: Він також працює, якщо він просто повертає нуль . Ви також можете використовувати тип опції.
ЖакБ

@JacquesB: але що робити, якщо null - дійсний елемент, що чекає?
JBSnorro

5

Немає нічого поганого в методі "Спробуйте ..." (маючи вихідний параметр) для сценарію, в якому збій (без значення) такий же поширений, як і успіх.

Але якщо ви наполягаєте на тому, щоб робити щось інакше, ви можете повернути порожнє повідомлення і просто надіслати це (вже не ваша проблема) або відкласти заяву if, поки ви дійсно не повинні знати, чи є у вас повідомлення зі змістом чи ні.

Зауважте, що хтось повинен зробити тест десь у будь-якому випадку. Я б стверджував, що тест слід робити, коли і де питання є поточним. Це в момент декерування.


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