Qual è il modo più elegante per scrivere un metodo "Try" in C # 7?


21

Sto scrivendo un tipo di implementazione della coda che ha un TryDequeuemetodo che utilizza un modello simile a vari TryParsemetodi .NET , in cui restituisco un valore booleano se l'azione ha outesito positivo e utilizzo un parametro per restituire il valore effettivo dequeued.

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

Ora, mi piace evitare i outparametri ogni volta che posso. C # 7 ci fornisce delcarazioni variabili per facilitare il lavoro con loro, ma continuo a considerare i params più un male necessario che uno strumento utile.

Il comportamento che desidero da questo metodo è il seguente:

  • Se c'è un oggetto da dequeue, restituiscilo.
  • Se non ci sono elementi da accodare (la coda è vuota), fornire al chiamante informazioni sufficienti per agire in modo appropriato.
  • Non restituire un articolo null solo se non ci sono articoli.
  • Non generare un'eccezione se si tenta di accodare da una coda vuota.

In questo momento, un chiamante di questo metodo userebbe quasi sempre un modello come il seguente (usando la sintassi della variabile out C # 7):

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

Non è il peggiore, tutto sommato. Ma non posso fare a meno di pensare che potrebbero esserci modi migliori per farlo (sono totalmente disposto a ammettere che potrebbe non esserci). Odio come il chiamante deve interrompere il suo flusso con un condizionale quando tutto ciò che vuole è ottenere un valore se ne esiste uno.

Quali sono alcuni altri schemi esistenti per realizzare questo stesso comportamento "provare"?

Per il contesto, questo metodo può potenzialmente essere chiamato nei progetti VB, quindi punti bonus per qualcosa che funzioni bene in entrambi. Tuttavia, questo fatto dovrebbe avere un peso molto ridotto.


9
Definire una Option<T>struttura e restituirla. Quelle bool Try(..., out data)funzioni sono un abominio.
Codici A Caos

2
Stavo pensando in modo simile ... Forse <T>, Opzione <T> se uno è contrario ai parametri OUT.
Jon Raynor,

1
@FrustratedWithFormsDesigner bene, non ho tanto "provato" altre cose (i giochi di parole sono i benvenuti), tanto quanto ci ho pensato. Ho avuto l'idea di restituire una ValueTuple ma, nella migliore delle ipotesi, non credo che abbia offerto molti miglioramenti.
Eric Sondergard,

@CodesInChaos Siamo sulla stessa pagina, ecco perché sono qui! E mi piace questa idea. Se hai tempo, ti andrebbe di dare qualche dettaglio in più in una risposta in modo che io possa accettarlo?
Eric Sondergard,

Risposte:


24

Utilizzare un tipo di opzione, vale a dire un oggetto di un tipo con due versioni, in genere chiamato "Some" (quando è presente un valore) o "None" (quando non è presente un valore) ... O occasionalmente si chiamano Just and Nothing. Esistono quindi funzioni di questi tipi che ti consentono di accedere al valore se è presente, verificare la presenza e, soprattutto, un metodo che ti consente di applicare una funzione che restituisce un'ulteriore Opzione al valore se è presente (in genere in C # questo dovrebbe si chiama FlatMap anche se in altre lingue viene spesso chiamato Bind invece ... Il nome è fondamentale in C # perché avendo il metodo s di questo nome e tipo si usano gli oggetti Option nelle istruzioni LINQ).

Funzionalità aggiuntive possono includere metodi come IfPresent e IfNotPresent per invocare azioni nelle condizioni pertinenti e OrElse (che sostituisce un valore predefinito quando non è presente alcun valore ma non è altrimenti operativo) e così via.

Il tuo esempio potrebbe quindi assomigliare a:

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

Questo è il modello monade Opzione (o Forse) ed è estremamente utile. Esistono implementazioni esistenti (ad es. Https://github.com/nlkl/Optional/blob/master/README.md ) ma non è difficile neanche per te.

(Potresti voler estendere questo modello in modo da restituire una descrizione della causa dell'errore invece di nulla quando il metodo fallisce ... Questo è interamente realizzabile e spesso viene chiamato Ouna monade; come suggerisce il nome puoi usare la stessa FlatMap modello per rendere facile lavorare anche in quel caso)


Questa è sicuramente una buona opzione, grazie per il suggerimento e i tuoi pensieri. Stavo davvero cercando di esplorare più idee qui.
Eric Sondergard,

2
La cosa bella di Option/ Maybeè che è una monade (se implementata come una, ovviamente), il che significa che può essere incatenata, spostata, non imballata ed elaborata in sicurezza senza mai dover gestire direttamente i diversi casi, e questo incatenamento e l'elaborazione può essere eseguita con le espressioni di query LINQ.
Jörg W Mittag,

3
A proposito, flatMap/ bindviene chiamato SelectManyin .NET.
Jörg W Mittag,

5
Mi chiedo se sarebbe una buona idea scegliere un nome diverso, poiché in questo modo infrangerai Try...le convenzioni di denominazione in .NET (e manutentori potenzialmente confusi).
Bob

@Bob Questo è un ottimo punto. Sono d'accordo.
Eric Sondergard,

12

Le risposte fornite sono buone e vorrei andare con una di esse. Considera questa risposta semplicemente riempiendo alcuni angoli con alcune idee alternative:

  • Crea sottoclassi di Messagechiamato SomeMessagee NoMessage. Dequeueora può tornare NoMessagese non è presente alcun messaggio e SomeMessagese è presente un messaggio. Se la persona interessata si preoccupa di rilevare il caso in cui si trova, può farlo facilmente mediante l'ispezione del tipo. Se non lo fanno, beh, semplicemente NoMessagenon fanno nulla ogni volta che viene chiamato uno dei suoi metodi e, ehi, ottengono ciò che hanno chiesto.

  • Come sopra, ma rendi Messageimplicitamente convertibile in bool(o implementa operator true / operator false). Ora puoi dire if (message)e far sì che sia vero se il messaggio è buono e falso se è cattivo. (Questa è quasi certamente una cattiva idea, ma la includo per completezza!)

  • C # 7 ha tuple. Restituisce una (bool, Message)tupla.

  • Crea Messageun tipo di struttura e ritorna Message?.


Ehi, grazie per la tua risposta. Con questa domanda stavo cercando di espormi a idee diverse come stavo cercando di trovare una soluzione al mio problema specifico. Saluti!
Eric Sondergard,

Voglio dire, se la tua "cattiva idea" viene utilizzata da Unity, perché non possiamo usarla?
Arturo Torres Sánchez,

@ ArturoTorresSánchez: la tua domanda è "qualcun altro ha usato una brutta pratica di programmazione, quindi perché non posso?" La domanda è incoerente. Nessuno ti impedisce di usare le stesse cattive pratiche di programmazione di qualcun altro. Vai avanti. Ciò non lo renderà una buona pratica in C #.
Eric Lippert,

Era la lingua sulla guancia. Immagino di dover usare "/ s" per contrassegnarlo.
Arturo Torres Sánchez,

@ ArturoTorresSánchez: questo è un sito di domande e risposte; Considero le domande come domande che cercano risposte .
Eric Lippert,

10

In C # 7 è possibile utilizzare la corrispondenza dei motivi per ottenere lo stesso in modo più elegante:

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

In questo caso particolare, probabilmente userò gli eventi piuttosto che eseguire il polling ripetutamente della coda.


3
Tuttavia, ciò non richiede TryDequeuedi restituire un'interfaccia marker con Messageun'implementazione e un'implementazione "nulla"? Certo, è più elegante nel sito di chiamata, ma sei bloccato nell'implementare 3 implementazioni nel codice reale, il che stesso potrebbe essere impossibile se Messageè un tipo esistente.
Telastyn,

1
@Telastyn: funziona anche se restituisce solo null . È inoltre possibile utilizzare un tipo di opzione.
JacquesB,

@JacquesB: ma cosa succede se null è un articolo accodabile valido?
JBSnorro,

5

Non c'è niente di sbagliato in un metodo Try ... (con un parametro out) per uno scenario in cui il fallimento (nessun valore) è altrettanto comune del successo.

Ma, se insisti a fare le cose in modo diverso, potresti voler restituire un messaggio vuoto e inviarlo (non più il tuo problema) o rimandare l'istruzione if fino a quando non devi davvero sapere se hai un messaggio con contenuto o meno.

Si noti che qualcuno deve comunque eseguire il test qualche volta. Direi che il test dovrebbe essere fatto quando e dove la domanda è attuale. Questo è al momento del dequeing.


Hai un buon punto. Devi ancora testare, qualunque cosa tu faccia. Onestamente, potrei ancora andare senza parametri a questo punto (in effetti sto attualmente esponendo più implementazioni "TryDequeue" proprio ora solo per giocherellare con ognuna di esse). Volevo solo discutere altre opzioni che potrebbero essere là fuori.
Eric Sondergard,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.