Restituisci valore magico, genera eccezioni o restituisci falso in caso di fallimento?


83

A volte finisco per dover scrivere un metodo o una proprietà per una libreria di classi per la quale non è eccezionale non avere una risposta reale, ma un errore. Qualcosa non può essere determinato, non è disponibile, non trovato, non è attualmente possibile o non ci sono più dati disponibili.

Penso che ci siano tre possibili soluzioni per una situazione relativamente non eccezionale per indicare un fallimento in C # 4:

  • restituisce un valore magico che non ha alcun significato altrimenti (come nulle -1);
  • generare un'eccezione (ad es. KeyNotFoundException);
  • restituire falsee fornire il valore restituito effettivo in un outparametro (ad esempio Dictionary<,>.TryGetValue).

Quindi le domande sono: in quale situazione non eccezionale dovrei lanciare un'eccezione? E se non dovessi lanciare: quando viene restituito un valore magico sopra riportato implementando un Try*metodo con un outparametro ? (Per me il outparametro sembra sporco, ed è più lavoro per usarlo correttamente.)

Sto cercando risposte concrete, come risposte che coinvolgono le linee guida di progettazione (non conosco alcun Try*metodo), usabilità (come chiedo questo per una biblioteca di classe), coerenza con il BCL e leggibilità.


Nella libreria di classi base di .NET Framework vengono utilizzati tutti e tre i metodi:

Si noti che come è Hashtablestato creato nel tempo in cui non c'erano generici in C #, utilizza objecte può quindi tornare nullcome valore magico. Ma con i generici vengono utilizzate le eccezioni Dictionary<,>e inizialmente non lo erano TryGetValue. Apparentemente le intuizioni cambiano.

Ovviamente, la dualità Item- TryGetValuee Parse- TryParseè lì per un motivo, quindi presumo che non sia stato fatto lanciare eccezioni per guasti non eccezionali in C # 4 . Tuttavia, i Try*metodi non esistevano sempre, anche quando Dictionary<,>.Itemesistevano.


6
"eccezioni significano bug". chiedere a un dizionario un elemento inesistente è un bug; chiedere a uno stream di leggere i dati quando si verifica un EOF ogni volta che si utilizza uno stream. (questo riassume la lunga risposta magnificamente formattata che non ho avuto la possibilità di inviare :))
KutuluMike

6
Non è che penso che la tua domanda sia troppo ampia, è che non è una domanda costruttiva. Non esiste una risposta canonica poiché le risposte stanno già mostrando.
George Stocker,

2
@GeorgeStocker Se la risposta fosse semplice e ovvia, non avrei posto la domanda. Il fatto che le persone possano argomentare perché una determinata scelta sia preferibile da qualsiasi punto di vista (come prestazioni, leggibilità, usabilità, manutenibilità, linee guida di progettazione o coerenza) la rende intrinsecamente non canonica ma rispondente alla mia soddisfazione. È possibile rispondere a tutte le domande in modo soggettivo. Apparentemente ti aspetti che una domanda abbia risposte per lo più simili per essere una buona domanda.
Daniel AA Pelsmaeker,

3
@Virtlink: George è un moderatore eletto dalla comunità che si offre volontariamente gran parte del suo tempo per aiutare a mantenere Stack Overflow. Ha dichiarato perché ha chiuso la domanda e le FAQ lo supportano.
Eric J.

15
La domanda appartiene qui, non perché è soggettiva, ma perché è concettuale. Regola empirica: se sei seduto di fronte al tuo codice IDE, chiedilo su Stack Overflow. Se ti trovi di fronte a un brainstorming sulla lavagna, chiedilo qui su Programmers.
Robert Harvey,

Risposte:


56

Non penso che i tuoi esempi siano davvero equivalenti. Esistono tre gruppi distinti, ognuno con la propria logica per il suo comportamento.

  1. Il valore magico è una buona opzione quando esiste una condizione "fino a" come StreamReader.Reado quando esiste un valore semplice da usare che non sarà mai una risposta valida (-1 per IndexOf).
  2. Generare un'eccezione quando la semantica della funzione è che il chiamante è sicuro che funzionerà. In questo caso una chiave inesistente o un cattivo formato doppio è davvero eccezionale.
  3. Utilizzare un parametro out e restituire un bool se la semantica deve sondare se l'operazione è possibile o meno.

Gli esempi forniti sono perfettamente chiari per i casi 2 e 3. Per i valori magici, si può discutere se questa è una buona decisione di progettazione o meno in tutti i casi.

Il NaNreso da Math.Sqrtè un caso speciale - segue lo standard in virgola mobile.


10
Non sono d'accordo con il numero 1. I valori magici non sono mai una buona opzione in quanto richiedono al programmatore successivo di conoscere il significato del valore magico. Hanno anche danneggiato la leggibilità. Non riesco a pensare a nessun caso in cui l'uso di un valore magico sia migliore del modello Try.
0b101010,

1
E la Either<TLeft, TRight>monade?
Sara

2
@ 0b101010: ho appena trascorso un po 'di tempo a cercare come streamreader.read può restituire in modo sicuro -1 ...
jmoreno

33

Stai tentando di comunicare all'utente dell'API cosa devono fare. Se lanci un'eccezione, non c'è niente che li costringe a catturarlo e solo leggere la documentazione farà sapere loro quali sono tutte le possibilità. Personalmente trovo lento e noioso scavare nella documentazione per trovare tutte le eccezioni che un certo metodo potrebbe lanciare (anche se è in intellisense, devo ancora copiarle manualmente).

Un valore magico richiede comunque di leggere la documentazione e, eventualmente, fare riferimento a una consttabella per decodificare il valore. Almeno non ha il sovraccarico di un'eccezione per ciò che si chiama un evento non eccezionale.

Ecco perché anche se a outvolte i parametri sono disapprovati, preferisco quel metodo, con la Try...sintassi. È la sintassi .NET canonica e C #. Stai comunicando all'utente dell'API che devono verificare il valore restituito prima di utilizzare il risultato. È inoltre possibile includere un secondo outparametro con un utile messaggio di errore, che aiuta nuovamente nel debug. Ecco perché voto per la soluzione Try...con outparametro.

Un'altra opzione è quella di restituire uno speciale oggetto "risultato", anche se lo trovo molto più noioso:

interface IMyResult
{
    bool Success { get; }
    // Only access this if Success is true
    MyOtherClass Result { get; }
    // Only access this if Success is false
    string ErrorMessage { get; }
}

Quindi la tua funzione sembra corretta perché ha solo parametri di input e restituisce solo una cosa. È solo che l'unica cosa che restituisce è una specie di tupla.

In effetti, se ti piace questo tipo di cose, puoi usare le nuove Tuple<>classi introdotte in .NET 4. Personalmente non mi piace il fatto che il significato di ciascun campo sia meno esplicito perché non posso dare Item1e Item2nomi utili.


3
Personalmente, io uso spesso un contenitore risultato come la vostra IMyResultcausa è possibile comunicare un risultato più complesso di un semplice trueo falseo il valore del risultato. Try*()è utile solo per cose semplici come la conversione da stringa a int.
stesso

1
Buon post. Per me, preferisco il linguaggio della struttura dei risultati che hai descritto sopra piuttosto che dividere tra i valori "return" e i parametri "out". Mantiene pulito e ordinato.
Ocean Airdrop,

2
Il problema con il secondo parametro di uscita è quando si hanno 50 funzioni in profondità in un programma complesso, come si fa a comunicare all'utente quel messaggio di errore? Generare un'eccezione è molto più semplice di avere livelli di errori di controllo. Quando ne ottieni uno, lancialo e non importa quanto sei profondo.
lancia il

@rolls - Quando usiamo oggetti di output, supponiamo che il chiamante immediato farà tutto ciò che vuole con l'output: trattalo localmente, ignoralo o fai bolle lanciando quello racchiuso in un'eccezione. È la migliore di entrambe le parole: il chiamante può vedere chiaramente tutti gli output possibili (con enumerazioni, ecc.), Può decidere cosa fare dell'errore e non è necessario provare a intercettare ogni chiamata. Se ti aspetti principalmente di gestire immediatamente il risultato o di ignorarlo, restituire gli oggetti è più semplice. Se vuoi lanciare tutti gli errori ai livelli superiori, le eccezioni sono più facili.
drizin,

2
Questo sta reinventando le eccezioni verificate in C # in un modo molto più noioso delle eccezioni controllate in Java.
Inverno,

17

Come già mostrano i tuoi esempi, ognuno di questi casi deve essere valutato separatamente e c'è un considerevole spettro di grigio tra "circostanze eccezionali" e "controllo del flusso", specialmente se il tuo metodo è destinato a essere riutilizzabile e potrebbe essere usato in schemi abbastanza diversi di quanto fosse originariamente progettato per. Non aspettatevi che tutti qui concordiamo sul significato di "non eccezionale", soprattutto se discutete immediatamente della possibilità di utilizzare "eccezioni" per implementarlo.

Potremmo anche non essere d'accordo su quale progetto renda il codice più facile da leggere e mantenere, ma presumo che il progettista della biblioteca abbia una chiara visione personale di ciò e debba solo bilanciarlo con le altre considerazioni coinvolte.

Risposta breve

Segui le tue sensazioni visive tranne quando stai progettando metodi abbastanza veloci e ti aspetti una possibilità di riutilizzo imprevisto.

Risposta lunga

Ogni chiamante futuro può tradurre liberamente tra codici di errore ed eccezioni come desidera in entrambe le direzioni; questo rende i due approcci di progettazione quasi equivalenti, tranne per le prestazioni, la facilità di debug e alcuni contesti di interoperabilità limitati. Questo di solito si riduce alle prestazioni, quindi concentriamoci su quello.

  • Come regola generale, aspettati che lanciare un'eccezione sia 200 volte più lento di un ritorno normale (in realtà, c'è una varianza significativa in questo).

  • Come altra regola empirica, lanciare un'eccezione può spesso consentire un codice molto più pulito rispetto al più grezzo dei valori magici, perché non si fa affidamento sul programmatore che traduce il codice di errore in un altro codice di errore, poiché viaggia attraverso più livelli di codice client verso un punto in cui esiste un contesto sufficiente per gestirlo in modo coerente e adeguato. (Caso speciale: nulltende ad andare meglio qui rispetto ad altri valori magici a causa della sua tendenza a tradursi automaticamente in un NullReferenceExceptioncaso di alcuni, ma non tutti i tipi di difetti; di solito, ma non sempre, abbastanza vicino alla fonte del difetto. )

Quindi qual è la lezione?

Per una funzione chiamata solo poche volte durante la vita di un'applicazione (come l'inizializzazione dell'app), usa tutto ciò che ti offre un codice più pulito e più comprensibile. Le prestazioni non possono essere motivo di preoccupazione.

Per una funzione usa e getta, usa tutto ciò che ti dà un codice più pulito. Quindi eseguire un po 'di profilazione (se necessario) e modificare le eccezioni ai codici di ritorno se rientrano tra i principali colli di bottiglia in base alle misurazioni o alla struttura generale del programma.

Per una costosa funzione riutilizzabile, usa tutto ciò che ti dà un codice più pulito. Se in pratica devi sempre subire un roundtrip di rete o analizzare un file XML su disco, il sovraccarico di generare un'eccezione è probabilmente trascurabile. È più importante non perdere i dettagli di eventuali guasti, nemmeno accidentalmente, piuttosto che tornare da un "guasto non eccezionale" in più tempo.

Una funzione riutilizzabile snella richiede più pensiero. Impiegando eccezioni, si sta forzando qualcosa come un rallentamento 100 volte sui chiamanti che vedranno l'eccezione su metà delle (molte) chiamate, se il corpo della funzione viene eseguito molto velocemente. Le eccezioni sono ancora un'opzione di progettazione, ma dovrai fornire un'alternativa bassa ai chiamanti che non possono permetterselo. Diamo un'occhiata a un esempio.

Elencate un ottimo esempio di Dictionary<,>.Item, che, in termini vaghi, è cambiato dal ritorno dei nullvalori al lancio KeyNotFoundExceptiontra .NET 1.1 e .NET 2.0 (solo se siete disposti a considerarlo il Hashtable.Itemsuo pratico precursore non generico). La ragione di questo "cambiamento" non è senza interesse qui. L'ottimizzazione delle prestazioni dei tipi di valore (non più boxe) ha reso il valore magico originale ( null) una non opzione; outi parametri riporterebbero solo una piccola parte del costo delle prestazioni. Quest'ultima considerazione sulle prestazioni è completamente trascurabile rispetto al sovraccarico del lancio di un KeyNotFoundException, ma il design dell'eccezione è ancora superiore qui. Perché?

  • i parametri di ref / out comportano costi ogni volta, non solo nel caso di "fallimento"
  • Chiunque si preoccupi può chiamare Containsprima di qualsiasi chiamata all'indicizzatore e questo schema è completamente naturale. Se uno sviluppatore vuole ma dimentica di chiamare Contains, non possono insinuarsi problemi di prestazioni; KeyNotFoundExceptionè abbastanza forte da essere notato e riparato.

Penso che 200x sia ottimista riguardo alla perfezione delle eccezioni ... vedi blogs.msdn.com/b/cbrumme/archive/2003/10/01/51524.aspx sezione "Performance and Trends" poco prima dei commenti.
gbjbaanb,

@gbjbaanb - Beh, forse. Quell'articolo usa un tasso di fallimento dell'1% per discutere l'argomento che non è un campo di gioco completamente diverso. I miei pensieri e le misurazioni vagamente ricordate provengono dal contesto del C ++ implementato in tabelle (vedere la Sezione 5.4.1.2 di questo rapporto , in cui un problema è che la prima eccezione del suo genere potrebbe iniziare con un errore di pagina (drastico e variabile ma ammortizzato una volta in testa.) Ma farò e pubblicherò un esperimento con .NET 4 e forse ottimizzerò questo valore di palla da baseball. Ho già sottolineato la varianza.
Jirka Hanika il

Il costo dei parametri di ref / out è quindi così elevato ? Come mai? E chiamare Containsprima di una chiamata a un indicizzatore potrebbe causare una race condition che non deve essere presente TryGetValue.
Daniel AA Pelsmaeker,

@gbjbaanb - Esperimento terminato. Ero pigro e usavo il mono su Linux. Le eccezioni mi hanno dato ~ 563000 tiri in 3 secondi. I ritorni mi hanno dato ~ 10900000 ritorni in 3 secondi. Sono 1:20, nemmeno 1: 200. Consiglio ancora di pensare 1: 100+ per qualsiasi codice più realistico. (la mia variante param, come avevo previsto, aveva costi trascurabili - in realtà sospetto che il jitter probabilmente avesse ottimizzato completamente la chiamata nel mio esempio minimalista se non fosse stata generata alcuna eccezione, indipendentemente dalla firma.)
Jirka Hanika

@Virtlink: concordato sulla sicurezza dei thread in generale, ma dato che in particolare si fa riferimento a .NET 4, utilizzare ConcurrentDictionaryper l'accesso multi-thread e Dictionaryper l'accesso single thread per prestazioni ottimali. Cioè, non usare `` Contains` NON rende sicuro il thread di codice con questa particolare Dictionaryclasse.
Jirka Hanika il

10

Qual è la cosa migliore da fare in una situazione relativamente non eccezionale per indicare un fallimento, e perché?

Non dovresti permettere il fallimento.

Lo so, è ondulato e idealista, ma ascoltami. Durante la progettazione, ci sono diversi casi in cui hai la possibilità di favorire una versione che non ha modalità di errore. Invece di avere un "FindAll" che non riesce, LINQ utilizza una clausola where che restituisce semplicemente un enumerabile vuoto. Invece di avere un oggetto che deve essere inizializzato prima dell'uso, consenti al costruttore di inizializzare l'oggetto (o inizializzare quando viene rilevato il non inizializzato). La chiave sta rimuovendo il ramo di errore nel codice consumatore. Questo è il problema, quindi concentrati su di esso.

Un'altra strategia per questo è lo KeyNotFoundsscenario. In quasi tutti i codebase su cui ho lavorato dalla 3.0 esiste qualcosa come questo metodo di estensione:

public static class DictionaryExtensions {
    public static V GetValue<K, V>(this IDictionary<K, V> arg, K key, Func<K,V> ifNotFound) {
        if (!arg.ContainsKey(key)) {
            return ifNotFound(key);
        }

        return arg[key];
    }
}

Non esiste una modalità di errore reale per questo. ConcurrentDictionaryha un simile GetOrAddincorporato.

Detto questo, ci saranno sempre momenti in cui è semplicemente inevitabile. Tutti e tre hanno il loro posto, ma preferirei la prima opzione. Nonostante tutto ciò che è fatto di pericolo nullo, è ben noto e si adatta a molti scenari "oggetto non trovato" o "risultato non applicabile" che compongono l'insieme "fallimento non eccezionale". Soprattutto quando si creano tipi di valori nulli, il significato di "questo potrebbe non riuscire" è molto esplicito nel codice e difficile da dimenticare / rovinare.

La seconda opzione è abbastanza buona quando l'utente fa qualcosa di stupido. Ti dà una stringa con il formato sbagliato, cerca di impostare la data al 42 dicembre ... qualcosa che vuoi che le cose esplodano rapidamente e in modo spettacolare durante i test in modo che il codice errato venga identificato e corretto.

L'ultima opzione è una che non mi piace. I parametri fuori sono imbarazzanti e tendono a violare alcune delle migliori pratiche quando si creano metodi, come concentrarsi su una cosa e non avere effetti collaterali. Inoltre, il parametro out è di solito significativo solo durante il successo. Detto questo, sono essenziali per alcune operazioni che sono generalmente vincolate da problemi di concorrenza o considerazioni sulle prestazioni (dove ad esempio non si desidera effettuare un secondo viaggio nel DB).

Se il valore di ritorno e il parametro out non sono banali, allora il suggerimento di Scott Whitlock su un oggetto risultato è preferito (come la Matchclasse Regex ).


7
La pipì dell'animale domestico qui: i outparametri sono completamente ortogonali al problema degli effetti collaterali. La modifica di un refparametro è un effetto collaterale e la modifica dello stato di un oggetto che si passa attraverso un parametro di input è un effetto collaterale, ma un outparametro è solo un modo imbarazzante per fare in modo che una funzione restituisca più di un valore. Non ci sono effetti collaterali, ma solo valori di ritorno multipli.
Scott Whitlock,

Ho detto che tendono a farlo , a causa di come le persone li usano. Come dici tu, sono semplicemente più valori di ritorno.
Telastyn,

Ma se non ti piacciono i parametri e usi le eccezioni per far esplodere le cose in modo spettacolare quando il formato è sbagliato ... come gestisci il caso in cui il tuo formato è input dell'utente? Quindi o un utente può far saltare in aria le cose, oppure si incorre in una penalità prestazionale nel lanciare e poi prendere un'eccezione. Giusto?
Daniel AA Pelsmaeker,

@virtlink con un metodo di convalida distinto. È necessario comunque fornire i messaggi corretti all'interfaccia utente prima che vengano inviati.
Telastyn,

1
Esiste un modello legittimo per i parametri out, ed è una funzione che ha sovraccarichi che restituiscono tipi diversi. La risoluzione del sovraccarico non funzionerà per i tipi restituiti, ma per i parametri di uscita.
Robert Harvey,

2

Preferisco sempre generare un'eccezione. Ha un'interfaccia uniforme tra tutte le funzioni che potrebbero non funzionare e indica un guasto il più rumoroso possibile, una proprietà molto desiderabile.

Si noti che Parsee TryParsenon sono proprio la stessa cosa a parte da modalità di guasto. Il fatto che TryParsepossa anche restituire il valore è in qualche modo ortogonale, davvero. Considera la situazione in cui, ad esempio, stai convalidando alcuni input. In realtà non ti importa quale sia il valore, purché sia ​​valido. E non c'è niente di sbagliato nell'offrire una sorta di IsValidFor(arguments)funzione. Ma può mai essere la primaria modalità di funzionamento.


4
Se hai a che fare con un gran numero di chiamate a un metodo, le eccezioni possono avere un enorme effetto negativo sulle prestazioni. Le eccezioni dovrebbero essere riservate a condizioni eccezionali e sarebbero perfettamente accettabili per la convalida dell'input del modulo, ma non per l'analisi di numeri da file di grandi dimensioni.
Robert Harvey,

1
Questa è un'esigenza più specialistica, non il caso generale.
DeadMG

2
Quindi dici tu. Ma hai usato la parola "sempre". :)
Robert Harvey,

@DeadMG, concordato con RobertHarvey anche se penso che la risposta sia stata eccessivamente votata verso il basso, se fosse stata modificata per riflettere "la maggior parte del tempo" e quindi indicare eccezioni (nessun gioco di parole previsto) al caso generale quando si tratta di alte prestazioni di uso frequente chiama per prendere in considerazione altre opzioni.
Gerald Davis,

Le eccezioni non sono costose. Catturare le eccezioni lanciate in profondità può essere costoso in quanto il sistema deve svolgere lo stack al punto critico più vicino. Ma quel "costoso" è relativamente piccolo e non dovrebbe essere temuto nemmeno in anelli nidificati stretti.
Matthew Whited,

2

Come altri hanno già notato, il valore magico (incluso un valore di ritorno booleano) non è una soluzione eccezionale, tranne che come marker di "fine gamma". Motivo: la semantica non è esplicita, anche se si esaminano i metodi dell'oggetto. Devi effettivamente leggere tutta la documentazione per l'intero oggetto fino a "oh sì, se restituisce -42 significa bla bla bla".

Questa soluzione può essere utilizzata per motivi storici o per motivi di prestazioni, ma dovrebbe essere altrimenti evitata.

Ciò lascia due casi generali: sondaggio o eccezioni.

Qui la regola empirica è che il programma non dovrebbe reagire alle eccezioni tranne che per gestire quando il programma / involontariamente / viola alcune condizioni. Il sondaggio dovrebbe essere usato per assicurarsi che ciò non accada. Pertanto, un'eccezione significa che il relativo sondaggio non è stato eseguito in anticipo o che è accaduto qualcosa di completamente inaspettato.

Esempio:

Vuoi creare un file da un determinato percorso.

È necessario utilizzare l'oggetto File per valutare in anticipo se questo percorso è legale per la creazione o la scrittura del file.

Se il tuo programma in qualche modo finisce ancora per cercare di scrivere su un percorso illegale o altrimenti non scrivibile, dovresti ricevere una scappatoia. Questo potrebbe accadere a causa di una condizione di competizione (qualche altro utente ha rimosso la directory o l'ha resa di sola lettura, dopo aver effettuato il probl)

Il compito di gestire un guasto imprevisto (segnalato da un'eccezione) e di verificare se le condizioni sono giuste per l'operazione in anticipo (sondaggio) di solito sarà strutturato in modo diverso e dovrebbe pertanto utilizzare meccanismi diversi.


0

Penso che il Trymodello sia la scelta migliore, quando il codice indica semplicemente cosa è successo. Odio param e mi piace l'oggetto nullable. Ho creato la seguente lezione

public sealed class Bag<TValue>
{
    public Bag(TValue value, bool hasValue = true)
    {
        HasValue = hasValue;
        Value = value;
    }

    public static Bag<TValue> Empty
    {
        get { return new Bag<TValue>(default(TValue), false); }
    }

    public bool HasValue { get; private set; }
    public TValue Value { get; private set; }
}

così posso scrivere il seguente codice

    public static Bag<XElement> GetXElement(this XElement element, string elementName)
    {
        try
        {
            XElement result = element.Element(elementName);
            return result == null
                       ? Bag<XElement>.Empty
                       : new Bag<XElement>(result);
        }
        catch (Exception)
        {
            return Bag<XElement>.Empty;
        }
    }

Sembra nullable ma non solo per il tipo di valore

Un altro esempio

    public static Bag<string> TryParseString(this XElement element, string attributeName)
    {
        Bag<string> attributeResult = GetString(element, attributeName);
        if (attributeResult.HasValue)
        {
            return new Bag<string>(attributeResult.Value);
        }
        return Bag<string>.Empty;
    }

    private static Bag<string> GetString(XElement element, string attributeName)
    {
        try
        {
            string result = element.GetAttribute(attributeName).Value;
            return new Bag<string>(result);
        }
        catch (Exception)
        {
            return Bag<string>.Empty;
        }
    }

3
try catchcauserà il caos sulla tua performance se stai chiamando GetXElement()e fallendo molte volte.
Robert Harvey,

a volte non importa. Dai un'occhiata a Bag call. Grazie per la tua osservazione

la tua borsa <T> clas è quasi identica a System.Nullable <T> aka "oggetto nullable"
aeroson

sì, quasi public struct Nullable<T> where T : structla differenza principale in un vincolo. tra cui l'ultima versione è qui github.com/Nelibur/Nelibur/blob/master/Source/Nelibur.Sword/…
GSerjo

0

Se sei interessato al percorso del "valore magico", un altro modo per risolverlo è sovraccaricare lo scopo della classe Lazy. Sebbene Lazy abbia lo scopo di posticipare l'istanza, nulla ti impedisce davvero di usare come forse o un'opzione. Per esempio:

    public static Lazy<TValue> GetValue<TValue, TKey>(
        this IDictionary<TKey, TValue> dictionary,
        TKey key)
    {
        TValue retVal;
        if (dictionary.TryGetValue(key, out retVal))
        {
            var retValRef = retVal;
            var lazy = new Lazy<TValue>(() => retValRef);
            retVal = lazy.Value;
            return lazy;
        }

        return new Lazy<TValue>(() => default(TValue));
    }
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.