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

19

Sto scrivendo un tipo di implementazione Queue che ha un metodo TryDequeue che usa un pattern simile a vari metodi .NET TryParse , dove restituisco un valore booleano se l'azione è riuscita e uso un parametro out per restituire il valore dequalificato effettivo.

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

Ora, mi piace evitare out params ogni volta che posso. C # 7 ci fornisce le delcarazioni variabili per semplificare il lavoro con esse, ma considero i params più un male necessario che uno strumento utile.

Il comportamento che voglio da questo metodo è il seguente:

  • Se è presente un elemento da riaccodare, restituirlo.
  • Se non ci sono elementi da rimettere in coda (la coda è vuota), fornire al chiamante informazioni sufficienti per agire in modo appropriato.
  • Non restituire solo un oggetto nullo se non ci sono elementi rimasti.
  • Non lanciare un'eccezione se stai tentando di disconnetterti da una coda vuota.

Al momento, un chiamante di questo metodo utilizza quasi sempre un modello come il seguente (usando la sintassi variabile C # 7):

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

Che non è il peggiore, tutto sommato. Ma non posso fare a meno di pensare che potrebbero esserci modi migliori per farlo (sono totalmente disposto a riconoscere 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 "try"?

Per il contesto, questo metodo potrebbe potenzialmente essere chiamato in progetti VB, quindi punti bonus per qualcosa che funziona bene in entrambi. Questo fatto dovrebbe comunque avere un peso molto basso.

    
posta Eric Sondergard 16.03.2017 - 18:10
fonte

4 risposte

23

Usa un tipo di opzione, ovvero un oggetto di un tipo con due versioni, in genere chiamato "Alcuni" (quando è presente un valore) o "Nessuno" (quando non c'è un valore). O a volte sono chiamati Just and Nothing. Esistono quindi funzioni in questi tipi che consentono di accedere al valore se è presente, verificare la presenza e, soprattutto, un metodo che consente di applicare una funzione che restituisce un'ulteriore Opzione al valore se è presente (in genere in C # ciò dovrebbe si chiama FlatMap anche se in altri linguaggi viene spesso chiamato Bind ... Il nome è fondamentale in C # perché, avendo il metodo s di questo nome e tipo, utilizziamo gli oggetti Option nelle istruzioni LINQ).

Altre funzionalità possono includere metodi come IfPresent e IfNotPresent per richiamare azioni nelle condizioni pertinenti e OrElse (che sostituisce un valore predefinito quando nessun valore è presente ma è un no op altrimenti) e così via.

Il tuo esempio potrebbe quindi somigliare a:

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

Questo è il modello monade Option (o Maybe), ed è estremamente utile. Esistono implementazioni esistenti (ad es. link ) ma non è difficile nemmeno per le tue.

(Potresti voler estendere questo modello in modo che tu restituisca una descrizione della causa dell'errore invece di nulla quando il metodo fallisce ... Questo è interamente raggiungibile ed è spesso chiamato l'o monade; come suggerisce il nome puoi usare lo stesso pattern di FlatMap per renderlo facile da usare anche in quel caso)

    
risposta data 16.03.2017 - 19:23
fonte
10

Le risposte date sono buone e vorrei andare con una di esse. Considera questa risposta per riempire solo alcuni angoli con alcune idee alternative:

  • Crea sottoclassi di Message chiamate SomeMessage e NoMessage . Dequeue ora può restituire NoMessage se non ci sono messaggi e SomeMessage se c'è un messaggio. Se il destinatario si preoccupa di individuare il caso in cui si trovano, possono farlo facilmente tramite l'ispezione del tipo. Se non lo fanno, beh, fai un NoMessage non fare nulla ogni volta che viene chiamato uno dei suoi metodi, e hey, ottengono ciò che hanno chiesto.

  • Uguale a quanto sopra, ma rende Message implicitamente convertibile in bool (o implementa operator true / operator false ). Ora puoi dire if (message) e farlo essere vero se il messaggio è buono e falso se è cattivo. (Questa è quasi certamente una cattiva idea, ma la includo per completezza!)

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

  • Crea Message un tipo di struct e restituisci Message? .

risposta data 20.03.2017 - 23:34
fonte
10

In C # 7 puoi utilizzare la corrispondenza dei pattern per ottenere lo stesso risultato in un 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 utilizzerei gli eventi piuttosto che il polling della coda ripetutamente.

    
risposta data 16.03.2017 - 18:49
fonte
4

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.

Tuttavia, se insisti a fare le cose in modo diverso, potresti voler restituire un messaggio vuoto e inviare semplicemente questo (non il tuo problema più) o posticipare l'istruzione if finché non devi veramente sapere se hai un messaggio con contenuto o no.

Nota che qualcuno deve fare il test prima o poi. Direi che il test dovrebbe essere fatto quando e dove la domanda è attuale. Questo è al momento del ritiro.

    
risposta data 16.03.2017 - 21:12
fonte

Leggi altre domande sui tag