È meglio restituire una raccolta nulla o vuota?


420

È una specie di domanda generale (ma sto usando C #), qual è il modo migliore (best practice), restituisci una collezione nulla o vuota per un metodo che ha una raccolta come tipo di ritorno?


5

3
@CPerkins - Sì, c'è. È chiaramente indicato nelle linee guida per la progettazione del framework .NET di Microsoft. Vedi la risposta di RichardOD per i dettagli.
Greg Beech,

51
SOLO se il significato è "Non riesco a calcolare i risultati" se si restituisce null. Null non dovrebbe mai avere la semantica di "vuoto", solo "mancante" o "sconosciuto". Maggiori dettagli nel mio articolo sull'argomento: blogs.msdn.com/ericlippert/archive/2009/05/14/…
Eric Lippert,

3
Bozho: "any" è un sacco di lingue. Che dire di Common Lisp, in cui l'elenco vuoto è esattamente uguale al valore null? :-)
Ken,

9
In realtà, il "duplicato" riguarda i metodi che restituiscono un oggetto, non una raccolta. È uno scenario diverso con risposte diverse.
GalacticCowboy il

Risposte:


499

Raccolta vuota. Sempre.

Questo fa schifo:

if(myInstance.CollectionProperty != null)
{
  foreach(var item in myInstance.CollectionProperty)
    /* arrgh */
}

È considerata una procedura consigliata non restituire MAI nullquando si restituisce una raccolta o un elenco. Restituire SEMPRE un enumerable / collection vuoto. Previene le suddette assurdità e impedisce che la tua auto venga colpita da colleghi e utenti delle tue classi.

Quando parli di proprietà, imposta sempre una volta la proprietà e dimenticala

public List<Foo> Foos {public get; private set;}

public Bar() { Foos = new List<Foo>(); }

In .NET 4.6.1, puoi condensare abbastanza:

public List<Foo> Foos { get; } = new List<Foo>();

Quando si parla di metodi che restituiscono enumerabili, è possibile restituire facilmente un enumerabile vuoto anziché null...

public IEnumerable<Foo> GetMyFoos()
{
  return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}

L'uso Enumerable.Empty<T>()può essere visto come più efficiente della restituzione, ad esempio, una nuova raccolta o matrice vuota.


24
Sono d'accordo con Will, ma penso che "sempre" sia un po 'eccessivo. Mentre una raccolta vuota potrebbe significare "0 articoli", la restituzione di Null potrebbe significare "nessuna raccolta", ad es. se stai analizzando HTML, cercando un <ul> con id = "foo", <ul id = "foo"> </ul> potrebbe restituire una raccolta vuota; se non c'è <ul> con id = "pippo" sarebbe meglio un ritorno nullo (a meno che tu non voglia gestire questo caso con un'eccezione)
Patonza,

30
non è sempre una questione se "puoi facilmente restituire un array vuoto", ma piuttosto se un array vuoto potrebbe essere fuorviante nel contesto attuale. Un array vuoto in realtà significa qualcosa, così come null. Dire che dovresti sempre restituire un array vuoto piuttosto che null, è quasi fuorviante quanto dirti che un metodo booleano dovrebbe sempre restituire vero. Entrambi i possibili valori trasmettono un significato.
David Hedlund,

31
In realtà dovresti preferire restituire System.Linq.Enumerable.Empty <Foo> () invece di un nuovo Foo [0]. È più esplicito e ti consente di risparmiare un'allocazione di memoria (almeno, nella mia implementazione .NET installata).
Trillian,

4
@Will: OP: "restituisci una collezione nulla o vuota per un metodo che ha una raccolta come tipo di ritorno". In questo caso penso che sia IEnumerableo ICollectionmeno così importante. Ad ogni modo, se selezioni qualcosa di tipo ICollectionanche loro ritornano null... Vorrei che restituissero una raccolta vuota, ma mi sono imbattuto in loro ritornando null, quindi ho pensato di menzionarlo qui. Direi che il valore predefinito di una raccolta di enumerabili è vuoto, non nullo. Non sapevo che fosse un argomento così delicato.
Matthijs Wessels,


154

Dalle Framework Design Guidelines 2nd Edition (pag. 256):

NON restituire valori null dalle proprietà della raccolta o dai metodi che restituiscono raccolte. Restituisce invece una raccolta vuota o una matrice vuota.

Ecco un altro articolo interessante sui vantaggi di non restituire null (stavo cercando di trovare qualcosa sul blog di Brad Abram, e lui si è collegato all'articolo).

Modifica- come Eric Lippert ha ora commentato la domanda originale, vorrei anche collegare il suo eccellente articolo .


33
+1. È sempre buona norma seguire le linee guida per la progettazione del framework a meno che non si abbia un'ottima ragione per non farlo.

@Will, assolutamente. Questo è quello che seguo. Non ho mai trovato alcun bisogno di fare diversamente
RichardOD,

sì, è quello che segui. Ma nei casi in cui le API su cui fai affidamento non la seguono, sei "intrappolato";)
Bozho,

@ Bozho- yeap. La tua risposta fornisce alcune belle risposte a quei casi marginali.
RichardOD,

90

Dipende dal tuo contratto e dal tuo caso concreto . Generalmente è meglio restituire raccolte vuote , ma a volte ( raramente ):

  • null potrebbe significare qualcosa di più specifico;
  • la tua API (contratto) potrebbe costringerti a tornare null.

Alcuni esempi concreti:

  • un componente dell'interfaccia utente (da una libreria fuori dal tuo controllo), potrebbe eseguire il rendering di una tabella vuota se viene passata una raccolta vuota o nessuna tabella, se viene passato null.
  • in un Object-to-XML (JSON / qualunque cosa), dove nullsignificherebbe che manca l'elemento, mentre una raccolta vuota renderebbe ridondante (e possibilmente errato)<collection />
  • stai utilizzando o implementando un'API che afferma esplicitamente che il valore null deve essere restituito / passato

4
@ Bozho- alcuni esempi interessanti qui.
RichardOD,

1
Vedrei un po 'il tuo punto, anche se non penso che dovresti costruire il tuo codice attorno ad altri difetti e per definizione' null 'in c # non significa mai qualcosa di specifico. Il valore null è definito come "nessuna informazione" e quindi dire che trasporta informazioni specifiche è un ossimetro. Ecco perché le linee guida .NET affermano che è necessario restituire un set vuoto se il set è effettivamente vuoto. il ritorno a null sta dicendo: "Non so dove sia andato il set previsto"
Rune FS,

13
no, significa "non c'è un set" piuttosto che "il set non ha elementi"
Bozho,

Ci credevo, poi ho dovuto scrivere un sacco di TSQL e ho imparato che non è sempre così, heheh.

1
La parte con Object-To-Xml è perfetta
Mihai Caracostea l'

36

C'è un altro punto che non è stato ancora menzionato. Considera il seguente codice:

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }

Il linguaggio C # restituirà un enumeratore vuoto quando chiama questo metodo. Pertanto, per essere coerente con il design del linguaggio (e, quindi, le aspettative del programmatore), è necessario restituire una raccolta vuota.


1
Tecnicamente non penso che questo ritorni una raccolta vuota.
FryGuy,

3
@FryGuy - No non lo fa. Restituisce un oggetto Enumerable, il cui metodo GetEnumerator () restituisce un Enumerator che è (per analisi con una raccolta) vuoto. Cioè, il metodo MoveNext () dell'enumeratore restituisce sempre false. La chiamata al metodo di esempio non restituisce null, né restituisce un oggetto Enumerable il cui metodo GetEnumerator () restituisce null.
Jeffrey L Whitledge,

31

Vuoto è molto più adatto ai consumatori.

Esiste un metodo chiaro per creare un enumerabile vuoto:

Enumerable.Empty<Element>()

2
Grazie per il trucco. Stavo istanziando una Lista vuota <T> () come un idiota, ma questo sembra molto più pulito ed è probabilmente anche un po 'più efficiente.
Jacobs Data Solutions,

18

Mi sembra che dovresti restituire il valore semanticamente corretto nel contesto, qualunque esso sia. Una regola che dice "restituisci sempre una raccolta vuota" mi sembra un po 'semplicistica.

Supponiamo, diciamo, in un sistema per un ospedale, che abbiamo una funzione che dovrebbe restituire un elenco di tutti i precedenti ricoveri negli ultimi 5 anni. Se il cliente non è stato in ospedale, ha senso restituire un elenco vuoto. Ma cosa succede se il cliente lascia vuota quella parte del modulo di ammissione? Abbiamo bisogno di un valore diverso per distinguere "lista vuota" da "nessuna risposta" o "non so". Potremmo generare un'eccezione, ma non è necessariamente una condizione di errore e non ci porta necessariamente fuori dal normale flusso del programma.

Sono stato spesso frustrato da sistemi che non sono in grado di distinguere tra zero e nessuna risposta. Ho avuto diverse volte in cui un sistema mi ha chiesto di inserire un numero, inserire zero e viene visualizzato un messaggio di errore che mi dice che devo inserire un valore in questo campo. Ho appena fatto: ho inserito zero! Ma non accetterà zero perché non può distinguerlo da nessuna risposta.


Rispondi a Saunders:

Sì, presumo che ci sia una differenza tra "La persona non ha risposto alla domanda" e "La risposta è stata zero". Questo era il punto dell'ultimo paragrafo della mia risposta. Molti programmi non sono in grado di distinguere "non lo so" da vuoto o zero, il che mi sembra un difetto potenzialmente grave. Ad esempio, stavo facendo shopping per una casa circa un anno fa. Sono andato su un sito web immobiliare e c'erano molte case elencate con un prezzo richiesto di $ 0. Mi è sembrato abbastanza buono: stanno regalando queste case gratuitamente! Ma sono sicuro che la triste realtà era che non avevano inserito il prezzo. In quel caso potresti dire: "Beh, OBVIOUSLY zero significa che non hanno inserito il prezzo - nessuno regalerà una casa gratuitamente". Ma il sito ha anche elencato i prezzi medi di domanda e vendita di case in varie città. Non posso fare a meno di chiedermi se la media non includesse gli zeri, dando così una media erroneamente bassa per alcuni luoghi. cioè qual è la media di $ 100.000; $ 120.000 e "non lo so"? Tecnicamente la risposta è "non lo so". Quello che probabilmente vogliamo davvero vedere è $ 110.000. Ma quello che probabilmente otterremo è $ 73.333, che sarebbe completamente sbagliato. Inoltre, se avessimo questo problema su un sito in cui gli utenti possono ordinare online? (Improbabile per gli immobili, ma sono sicuro che tu l'abbia visto fatto per molti altri prodotti.) Vorremmo davvero che il "prezzo non ancora specificato" venga interpretato come "gratuito"? dando così una media erroneamente bassa per alcuni luoghi. cioè qual è la media di $ 100.000; $ 120.000 e "non lo so"? Tecnicamente la risposta è "non lo so". Quello che probabilmente vogliamo davvero vedere è $ 110.000. Ma quello che probabilmente otterremo è $ 73.333, che sarebbe completamente sbagliato. Inoltre, se avessimo questo problema su un sito in cui gli utenti possono ordinare online? (Improbabile per gli immobili, ma sono sicuro che tu l'abbia visto fatto per molti altri prodotti.) Vorremmo davvero che il "prezzo non ancora specificato" venga interpretato come "gratuito"? dando così una media erroneamente bassa per alcuni luoghi. cioè qual è la media di $ 100.000; $ 120.000 e "non lo so"? Tecnicamente la risposta è "non lo so". Quello che probabilmente vogliamo davvero vedere è $ 110.000. Ma quello che probabilmente otterremo è $ 73.333, che sarebbe completamente sbagliato. Inoltre, se avessimo questo problema su un sito in cui gli utenti possono ordinare online? (Improbabile per gli immobili, ma sono sicuro che tu l'abbia visto fatto per molti altri prodotti.) Vorremmo davvero che il "prezzo non ancora specificato" venga interpretato come "gratuito"? che sarebbe completamente sbagliato. Inoltre, se avessimo questo problema su un sito in cui gli utenti possono ordinare online? (Improbabile per gli immobili, ma sono sicuro che tu l'abbia visto fatto per molti altri prodotti.) Vorremmo davvero che il "prezzo non ancora specificato" venga interpretato come "gratuito"? che sarebbe completamente sbagliato. Inoltre, se avessimo questo problema su un sito in cui gli utenti possono ordinare online? (Improbabile per gli immobili, ma sono sicuro che tu l'abbia visto fatto per molti altri prodotti.) Vorremmo davvero che il "prezzo non ancora specificato" venga interpretato come "gratuito"?

RE con due funzioni separate, un "ce n'è?" e un "se sì, che cos'è?" Sì, sicuramente potresti farlo, ma perché dovresti farlo? Ora il programma chiamante deve effettuare due chiamate anziché una. Cosa succede se un programmatore non riesce a chiamare il "qualsiasi?" e va dritto al "che cos'è?" ? Il programma restituirà uno zero errato? Generare un'eccezione? Restituire un valore indefinito? Crea più codice, più lavoro e più potenziali errori.

L'unico vantaggio che vedo è che ti consente di rispettare una regola arbitraria. C'è qualche vantaggio in questa regola che vale la pena di obbedire? In caso contrario, perché preoccuparsi?


Rispondi a Jammycakes:

Considera come sarebbe il codice reale. So che la domanda diceva C # ma mi scusi se scrivo Java. Il mio C # non è molto nitido e il principio è lo stesso.

Con un ritorno null:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}

Con una funzione separata:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}

In realtà è una riga o due in meno di codice con il ritorno null, quindi non è più un onere per il chiamante, è meno.

Non vedo come si crea un problema SECCO. Non è che dobbiamo eseguire la chiamata due volte. Se avessimo sempre voluto fare la stessa cosa quando l'elenco non esiste, forse potremmo spingere la gestione verso il basso fino alla funzione get-list piuttosto che fare in modo che il chiamante lo faccia, e quindi inserire il codice nel chiamante sarebbe una violazione DRY. Ma quasi sicuramente non vogliamo fare sempre la stessa cosa. Nelle funzioni in cui è necessario disporre dell'elenco da elaborare, un elenco mancante è un errore che potrebbe interrompere l'elaborazione. Ma su una schermata di modifica, sicuramente non vogliamo interrompere l'elaborazione se non hanno ancora inserito dati: vogliamo consentire loro di inserire i dati. Pertanto, la gestione di "nessuna lista" deve essere eseguita a livello di chiamante in un modo o nell'altro. E se lo facciamo con un ritorno nullo o una funzione separata non fa differenza per il principio più grande.

Certo, se il chiamante non controlla null, il programma potrebbe fallire con un'eccezione puntatore null. Ma se esiste una funzione "get any" separata e il chiamante non chiama quella funzione ma chiama ciecamente la funzione "get list", allora cosa succede? Se genera un'eccezione o altrimenti fallisce, beh, è ​​praticamente lo stesso di ciò che accadrebbe se restituisse null e non lo verificasse. Se restituisce un elenco vuoto, è semplicemente sbagliato. Non riesci a distinguere tra "Ho un elenco con zero elementi" e "Non ho un elenco". È come restituire zero per il prezzo quando l'utente non ha inserito alcun prezzo: è semplicemente sbagliato.

Non vedo come sia utile allegare un attributo aggiuntivo alla raccolta. Il chiamante deve ancora controllarlo. In che modo è meglio che verificare la presenza di null? Ancora una volta, la cosa peggiore in assoluto che potrebbe accadere è che il programmatore dimentichi di controllarlo e di dare risultati errati.

Una funzione che restituisce null non è una sorpresa se il programmatore ha familiarità con il concetto di significato null "non avere un valore", di cui penso che qualsiasi programmatore competente avrebbe dovuto sentir parlare, sia che pensi che sia una buona idea o meno. Penso che avere una funzione separata sia più un problema di "sorpresa". Se un programmatore non ha familiarità con l'API, quando esegue un test senza dati scoprirà rapidamente che a volte viene restituito un valore nullo. Ma come avrebbe scoperto l'esistenza di un'altra funzione se non gli fosse venuto in mente che potrebbe esserci una tale funzione e controlla la documentazione e la documentazione è completa e comprensibile? Preferirei di gran lunga avere una funzione che mi dà sempre una risposta significativa, piuttosto che due funzioni che devo conoscere e ricordare di chiamare entrambe.


4
Perché è necessario combinare le risposte "nessuna risposta" e "zero" nello stesso valore restituito? Invece, il metodo deve restituire "eventuali precedenti ricoveri negli ultimi cinque anni" e avere un metodo separato che chiede "è stato mai compilato l'elenco dei ricoveri precedenti?". Ciò presuppone che ci sia una differenza tra un elenco compilato senza precedenti ricoveri e un elenco non compilato.
John Saunders,

2
Ma se si restituisce null, si sta già caricando un onere aggiuntivo sul chiamante! Il chiamante deve verificare che ogni valore restituito sia nullo, una violazione DRY orribile. Se si desidera indicare "il chiamante non ha risposto alla domanda" separatamente, è più semplice creare una chiamata di metodo aggiuntiva per indicare il fatto. O quello, o utilizzare un tipo di raccolta derivata con una proprietà aggiuntiva di DeclinedToAnswer. La regola di non restituire mai nulla non è affatto arbitraria, è il Principio della minima sorpresa. Inoltre, il tipo restituito dal tuo metodo dovrebbe significare ciò che dice il suo nome. Quasi certamente no.
Jammycakes,

2
Stai assumendo che getHospitalizationList sia chiamato solo da un posto e / o che tutti i suoi chiamanti vorranno distinguere tra i casi "nessuna risposta" e "zero". Ci saranno casi (quasi certamente una maggioranza) in cui il tuo chiamante non ha bisogno di fare questa distinzione, e quindi li stai forzando ad aggiungere controlli nulli in luoghi in cui ciò non dovrebbe essere necessario. Ciò aggiunge un rischio significativo al tuo codebase perché le persone dimenticano troppo facilmente di farlo - e poiché ci sono pochissime ragioni legittime per restituire null invece di una raccolta, questo sarà molto più probabile.
Jammycakes,

3
RE il nome: nessun nome di funzione può descrivere completamente ciò che fa la funzione a meno che non sia lungo quanto la funzione, e quindi selvaggiamente poco pratico. Ma in ogni caso, se la funzione restituisce un elenco vuoto quando non è stata fornita alcuna risposta, quindi con lo stesso ragionamento, non dovrebbe essere chiamato "getHospitalizationListOrEmptyListIfNoAnswer"? Ma davvero, vorresti insistere sul fatto che la funzione Java Reader.read dovrebbe essere rinominata readCharacterOrReturnMinusOneOnEndOfStream? Che ResultSet.getInt dovrebbe davvero essere "getIntOrZeroIfValueWasNull"? Ecc.
Jay,

4
RE ogni chiamata vuole distinguere: beh, sì, suppongo che, o almeno che l'autore di un chiamante dovrebbe prendere una decisione consapevole che non gli interessa. Se la funzione restituisce un elenco vuoto per "non lo so" e i chiamanti trattano ciecamente questo "nessuno", potrebbero dare risultati seriamente inaccurati. Immagina se la funzione fosse "getAllergicReactionToMedicationList". Un programma che "elabora" alla cieca non è stato inserito come "il paziente non ha reazioni allergiche note" potrebbe letteralmente provocare l'uccisione di un paziente. Otterresti risultati simili, anche se meno drammatici, in molti altri sistemi. ...
Jay,

10

Se una raccolta vuota ha senso semanticamente, è quello che preferisco restituire. Restituire una raccolta vuota per GetMessagesInMyInbox()comunica "in realtà non ci sono messaggi nella posta in arrivo", mentre la restituzione nullpotrebbe essere utile per comunicare che sono disponibili dati insufficienti per dire come dovrebbe apparire l'elenco che potrebbe essere restituito.


6
Nell'esempio fornito, sembra che il metodo dovrebbe generare un'eccezione se non è in grado di soddisfare la richiesta anziché restituire null. Le eccezioni sono molto più utili per la diagnosi dei problemi rispetto ai valori null.
Greg Beech,

bene sì, nell'esempio della posta in arrivo un nullvalore sicuramente non sembra ragionevole, stavo pensando in termini più generali su quello. Le eccezioni sono ottime anche per comunicare il fatto che qualcosa è andato storto, ma se i "dati insufficienti" citati sono perfettamente previsti, allora lanciare un'eccezione avrebbe un design scadente. Sto piuttosto pensando a uno scenario in cui è perfettamente possibile e nessun errore per il metodo a volte non essere in grado di calcolare una risposta.
David Hedlund,

6

La restituzione di null potrebbe essere più efficiente, poiché non viene creato alcun nuovo oggetto. Tuttavia, spesso richiederebbe anche un nullcontrollo (o una gestione delle eccezioni).

Semanticamente nulle un elenco vuoto non significano la stessa cosa. Le differenze sono sottili e una scelta potrebbe essere migliore dell'altra in casi specifici.

Indipendentemente dalla tua scelta, documentalo per evitare confusione.


8
L'efficienza non dovrebbe quasi mai essere un fattore quando si considera la correttezza della progettazione di un'API. In alcuni casi molto specifici come le primitive grafiche, allora potrebbe essere così, ma quando ho a che fare con elenchi e molte altre cose di alto livello, ne dubito moltissimo.
Greg Beech,

Concordo con Greg, soprattutto se si considera che il codice che l'utente API deve scrivere per compensare questa "ottimizzazione" potrebbe essere più inefficiente rispetto al primo utilizzo di un design migliore.
Craig Stuntz,

D'accordo, e nella maggior parte dei casi semplicemente non vale l'ottimizzazione. Le liste vuote sono praticamente gratuite con la moderna gestione della memoria.
Jason Baker,

6

Si potrebbe sostenere che il ragionamento dietro Null Object Pattern è simile a quello a favore della restituzione della raccolta vuota.


4

Dipende dalla situazione. Se si tratta di un caso speciale, restituire null. Se la funzione restituisce semplicemente una raccolta vuota, ovviamente la restituzione è ok. Tuttavia, restituire una raccolta vuota come caso speciale a causa di parametri non validi o altri motivi NON è una buona idea, perché sta mascherando una condizione di caso speciale.

In realtà, in questo caso di solito preferisco lanciare un'eccezione per assicurarmi che NON sia VERAMENTE ignorato :)

Dire che rende il codice più robusto (restituendo una raccolta vuota) in quanto non devono gestire la condizione nulla è male, in quanto è semplicemente mascherare un problema che dovrebbe essere gestito dal codice chiamante.


4

Direi che nullnon è la stessa cosa di una collezione vuota e dovresti scegliere quale rappresenta meglio ciò che stai restituendo. Nella maggior parte dei casi nullè nulla (tranne in SQL). Una collezione vuota è qualcosa, anche se qualcosa di vuoto.

Se devi scegliere l'uno o l'altro, direi che dovresti tendere verso una collezione vuota piuttosto che nulla. Ma ci sono momenti in cui una raccolta vuota non è la stessa cosa di un valore nullo.


4

Pensa sempre a favore dei tuoi clienti (che usano la tua API):

Restituire 'null' molto spesso crea problemi con i client che non gestiscono correttamente i controlli null, causando una NullPointerException durante il runtime. Ho visto casi in cui un tale controllo null mancante ha forzato un problema di produzione prioritaria (un client utilizzato foreach (...) su un valore null). Durante il test il problema non si è verificato, poiché i dati operati erano leggermente diversi.


3

Mi piace dare spiegazioni qui, con un esempio adeguato.

Prendi in considerazione un caso qui ...

int totalValue = MySession.ListCustomerAccounts()
                          .FindAll(ac => ac.AccountHead.AccountHeadID 
                                         == accountHead.AccountHeadID)
                          .Sum(account => account.AccountValue);

Qui considera le funzioni che sto usando ..

1. ListCustomerAccounts() // User Defined
2. FindAll()              // Pre-defined Library Function

Posso facilmente usare ListCustomerAccounte FindAllinvece di.,

int totalValue = 0; 
List<CustomerAccounts> custAccounts = ListCustomerAccounts();
if(custAccounts !=null ){
  List<CustomerAccounts> custAccountsFiltered = 
        custAccounts.FindAll(ac => ac.AccountHead.AccountHeadID 
                                   == accountHead.AccountHeadID );
   if(custAccountsFiltered != null)
      totalValue = custAccountsFiltered.Sum(account => 
                                            account.AccountValue).ToString();
}

NOTA: poiché AccountValue non lo è null, la funzione Sum () non verrà restituita null. Quindi posso usarla direttamente.


2

Abbiamo discusso questa discussione tra il team di sviluppo al lavoro circa una settimana fa e siamo quasi all'unanimità per la raccolta vuota. Una persona voleva restituire null per lo stesso motivo indicato in precedenza da Mike.


2

Collezione vuota. Se stai usando C #, il presupposto è che massimizzare le risorse di sistema non è essenziale. Sebbene meno efficiente, restituire Empty Collection è molto più conveniente per i programmatori coinvolti (per il motivo di cui sopra).


2

Restituire una raccolta vuota è meglio nella maggior parte dei casi.

Il motivo è la convenienza dell'implementazione del chiamante, un contratto coerente e un'implementazione più semplice.

Se un metodo restituisce null per indicare un risultato vuoto, il chiamante deve implementare un adattatore di controllo null oltre all'enumerazione. Questo codice viene quindi duplicato in vari chiamanti, quindi perché non inserire questo adattatore nel metodo in modo che possa essere riutilizzato.

Un utilizzo valido di null per IEnumerable potrebbe essere un'indicazione di risultato assente o di un fallimento dell'operazione, ma in questo caso dovrebbero essere prese in considerazione altre tecniche, come il lancio di un'eccezione.

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace StackOverflow.EmptyCollectionUsageTests.Tests
{
    /// <summary>
    /// Demonstrates different approaches for empty collection results.
    /// </summary>
    class Container
    {
        /// <summary>
        /// Elements list.
        /// Not initialized to an empty collection here for the purpose of demonstration of usage along with <see cref="Populate"/> method.
        /// </summary>
        private List<Element> elements;

        /// <summary>
        /// Gets elements if any
        /// </summary>
        /// <returns>Returns elements or empty collection.</returns>
        public IEnumerable<Element> GetElements()
        {
            return elements ?? Enumerable.Empty<Element>();
        }

        /// <summary>
        /// Initializes the container with some results, if any.
        /// </summary>
        public void Populate()
        {
            elements = new List<Element>();
        }

        /// <summary>
        /// Gets elements. Throws <see cref="InvalidOperationException"/> if not populated.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>.</returns>
        public IEnumerable<Element> GetElementsStrict()
        {
            if (elements == null)
            {
                throw new InvalidOperationException("You must call Populate before calling this method.");
            }

            return elements;
        }

        /// <summary>
        /// Gets elements, empty collection or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with zero or more elements, or null in some cases.</returns>
        public IEnumerable<Element> GetElementsInconvenientCareless()
        {
            return elements;
        }

        /// <summary>
        /// Gets elements or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with elements, or null in case of empty collection.</returns>
        /// <remarks>We are lucky that elements is a List, otherwise enumeration would be needed.</remarks>
        public IEnumerable<Element> GetElementsInconvenientCarefull()
        {
            if (elements == null || elements.Count == 0)
            {
                return null;
            }
            return elements;
        }
    }

    class Element
    {
    }

    /// <summary>
    /// http://stackoverflow.com/questions/1969993/is-it-better-to-return-null-or-empty-collection/
    /// </summary>
    class EmptyCollectionTests
    {
        private Container container;

        [SetUp]
        public void SetUp()
        {
            container = new Container();
        }

        /// <summary>
        /// Forgiving contract - caller does not have to implement null check in addition to enumeration.
        /// </summary>
        [Test]
        public void UseGetElements()
        {
            Assert.AreEqual(0, container.GetElements().Count());
        }

        /// <summary>
        /// Forget to <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void WrongUseOfStrictContract()
        {
            container.GetElementsStrict().Count();
        }

        /// <summary>
        /// Call <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        public void CorrectUsaOfStrictContract()
        {
            container.Populate();
            Assert.AreEqual(0, container.GetElementsStrict().Count());
        }

        /// <summary>
        /// Inconvenient contract - needs a local variable.
        /// </summary>
        [Test]
        public void CarefulUseOfCarelessMethod()
        {
            var elements = container.GetElementsInconvenientCareless();
            Assert.AreEqual(0, elements == null ? 0 : elements.Count());
        }

        /// <summary>
        /// Inconvenient contract - duplicate call in order to use in context of an single expression.
        /// </summary>
        [Test]
        public void LameCarefulUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless() == null ? 0 : container.GetElementsInconvenientCareless().Count());
        }

        [Test]
        public void LuckyCarelessUseOfCarelessMethod()
        {
            // INIT
            var praySomeoneCalledPopulateBefore = (Action)(()=>container.Populate());
            praySomeoneCalledPopulateBefore();

            // ACT //ASSERT
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Excercise <see cref="ArgumentNullException"/> because of null passed to <see cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})"/>
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void UnfortunateCarelessUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Demonstrates the client code flow relying on returning null for empty collection.
        /// Exception is due to <see cref="Enumerable.First{TSource}(System.Collections.Generic.IEnumerable{TSource})"/> on an empty collection.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void UnfortunateEducatedUseOfCarelessMethod()
        {
            container.Populate();
            var elements = container.GetElementsInconvenientCareless();
            if (elements == null)
            {
                Assert.Inconclusive();
            }
            Assert.IsNotNull(elements.First());
        }

        /// <summary>
        /// Demonstrates the client code is bloated a bit, to compensate for implementation 'cleverness'.
        /// We can throw away the nullness result, because we don't know if the operation succeeded or not anyway.
        /// We are unfortunate to create a new instance of an empty collection.
        /// We might have already had one inside the implementation,
        /// but it have been discarded then in an effort to return null for empty collection.
        /// </summary>
        [Test]
        public void EducatedUseOfCarefullMethod()
        {
            Assert.AreEqual(0, (container.GetElementsInconvenientCarefull() ?? Enumerable.Empty<Element>()).Count());
        }
    }
}

2

Lo chiamo il mio errore da miliardi di dollari ... A quel tempo, stavo progettando il primo sistema di tipi completo per riferimenti in un linguaggio orientato agli oggetti. Il mio obiettivo era quello di garantire che ogni uso dei riferimenti fosse assolutamente sicuro, con il controllo eseguito automaticamente dal compilatore. Ma non ho resistito alla tentazione di inserire un riferimento null, semplicemente perché era così facile da implementare. Ciò ha portato a innumerevoli errori, vulnerabilità e arresti anomali del sistema, che probabilmente hanno causato un miliardo di dollari di dolore e danni negli ultimi quarant'anni. - Tony Hoare, inventore di ALGOL W.

Vedi qui per un'elaborata tempesta di merda nullin generale. Non sono d'accordo con l'affermazione che undefinedè un'altra null, ma vale comunque la pena leggere. E spiega, perché dovresti evitare del nulltutto e non solo nel caso che hai chiesto. L'essenza è che nullè in ogni lingua un caso speciale. Devi pensare nullcome un'eccezione. undefinedè diverso in questo modo, quel codice che si occupa di comportamenti indefiniti è nella maggior parte dei casi solo un bug. C e la maggior parte delle altre lingue hanno anche un comportamento indefinito, ma la maggior parte di esse non ha un identificatore per questo nella lingua.


1

Dal punto di vista della gestione della complessità, un obiettivo primario di ingegneria del software, vogliamo evitare la propagazione non necessaria complessità ciclomatiche ai clienti di un'API. Restituire un null al client è come restituirgli il costo della complessità ciclomatica di un altro ramo di codice.

(Ciò corrisponde a un onere di test unitario. Dovresti scrivere un test per il caso di reso nullo, oltre al caso di reso di raccolta vuoto.)

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.