Modi unici di utilizzare l'operatore Null Coalescing [chiuso]


164

Conosco il modo standard di utilizzare l'operatore Null coalescing in C # per impostare valori predefiniti.

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // returns Mr. T
anybody = somebody ?? "Mr. T"; // returns "Bob Saget"

Ma cos'altro può ??essere usato? Non sembra utile come l'operatore ternario, a parte essere più conciso e più facile da leggere di:

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget

Quindi, dato che pochi conoscono persino l'operatore di coalescenza nulla ...

  • Hai usato ??qualcos'altro?

  • È ??necessario o dovresti semplicemente usare l'operatore ternario (che la maggior parte ha familiarità)

Risposte:


216

Bene, prima di tutto, è molto più facile concatenare rispetto al ternario standard:

string anybody = parm1 ?? localDefault ?? globalDefault;

vs.

string anyboby = (parm1 != null) ? parm1 
               : ((localDefault != null) ? localDefault 
               : globalDefault);

Funziona bene anche se l'oggetto null-possible non è una variabile:

string anybody = Parameters["Name"] 
              ?? Settings["Name"] 
              ?? GlobalSetting["Name"];

vs.

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];

12
Il concatenamento è un grande vantaggio per l'operatore, rimuove un sacco di IF ridondanti
chakrit

1
L'ho usato oggi per sostituire un semplice blocco IF che ho scritto prima di sapere dell'operatore di coalescenza ternario o nullo. I rami vero e falso dell'istruzione IF originale hanno chiamato lo stesso metodo, sostituendo uno dei suoi argomenti con un valore diverso se un determinato input è NULL. Con l'operatore null coalescente, è una chiamata. Questo è davvero potente quando hai un metodo che ha bisogno di due o più di tali sostituzioni!
David A. Gray,

177

L'ho usato come un liner carico pigro:

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

Leggibile? Decidi tu stesso.


6
Hmmm, hai trovato un controesempio al "perché qualcuno dovrebbe usarlo come un offuscato IF" ... che in realtà è molto leggibile per me.
Godeke,

6
Potrei mancare qualcosa (uso principalmente Java), ma non c'è una condizione di competizione lì?
Justin K,

9
@Justin K: esiste una condizione di competizione solo se più thread accedono alla proprietà LazyProp dello stesso oggetto. È facilmente risolvibile con un lucchetto, se è richiesta la sicurezza thread di ogni istanza. Chiaramente in questo esempio, non è richiesto.
Jeffrey L Whitledge,

5
@Jeffrey: Se fosse chiaro, non avrei posto la domanda. :) Quando ho visto quell'esempio, ho immediatamente pensato a un membro singleton, e dato che mi capita di fare la maggior parte della mia codifica in un ambiente multithread ... Ma, sì, se assumiamo che il codice sia corretto, qualsiasi cosa in più non è necessaria.
Justin K,

7
Non deve essere un Singleton per avere una condizione di gara. Solo un'istanza condivisa della classe che contiene LazyProp e più thread che accedono a LazyProp. Lazy <T> è un modo migliore per fare questo tipo di cose ed è thread-safe di default (puoi scegliere di modificare la sicurezza del thread di Lazy <T>).
Niall Connaughton,

52

L'ho trovato utile in due modi "leggermente strani":

  • In alternativa per avere un outparametro durante la scrittura di TryParseroutine (ovvero restituire il valore null se l'analisi non riesce)
  • Come rappresentazione "non so" per i confronti

Quest'ultimo ha bisogno di maggiori informazioni. In genere, quando si crea un confronto con più elementi, è necessario vedere se la prima parte del confronto (ad es. Età) fornisce una risposta definitiva, quindi la parte successiva (ad es. Nome) solo se la prima parte non ha aiutato. L'uso dell'operatore null coalescing significa che è possibile scrivere confronti piuttosto semplici (sia per l'ordinamento che per l'uguaglianza). Ad esempio, utilizzando un paio di classi di supporto in MiscUtil :

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

Devo ammettere che ora ho ProjectionComparer in MiscUtil, insieme ad alcune estensioni, che rendono questo tipo di cose ancora più semplice, ma è comunque pulito.

Lo stesso può essere fatto per verificare l'uguaglianza di riferimento (o nullità) all'inizio dell'implementazione di Equals.


Mi piace quello che hai fatto con PartialComparer, ma stavo cercando casi in cui ho bisogno di mantenere le variabili di espressione valutate. Non sono esperto di lambda ed estensioni, quindi potresti vedere se quanto segue aderisce a un modello simile (cioè funziona)? stackoverflow.com/questions/1234263/#1241780
maxwellb

33

Un altro vantaggio è che l'operatore ternario richiede una doppia valutazione o una variabile temporanea.

Considera questo, ad esempio:

string result = MyMethod() ?? "default value";

mentre con l'operatore ternario ti rimane uno dei due:

string result = (MyMethod () != null ? MyMethod () : "default value");

che chiama MyMethod due volte o:

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

Ad ogni modo, l'operatore a coalescenza nulla è più pulito e, immagino, più efficiente.


1
+1. Questa è una grande ragione per cui mi piace l'operatore null coalescente. È particolarmente utile quando la chiamata MyMethod()ha effetti collaterali.
un CVn,

Se MyMethod()non ha effetti al di fuori della restituzione di un valore, il compilatore sa di non chiamarlo due volte, quindi nella maggior parte dei casi non devi preoccuparti dell'efficienza qui.
TinyTimZamboni,

Mantiene anche le cose più leggibili IMHO quando MyMethod()è una sequenza incatenata di oggetti punteggiati. Esempio:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore,

@TinyTimZamboni, hai un riferimento per questo comportamento del compilatore?
Kuba Wyrostek,

@KubaWyrostek Non ho conoscenza del funzionamento specifico del compilatore C #, ma ho una certa esperienza con la teoria del compilatore statico con llvm. Esistono diversi approcci che un compilatore può adottare per ottimizzare una chiamata come questa. La numerazione di valore globale noterà che le due chiamate MyMethodsono identiche in questo contesto, supponendo che MyMethodsia una funzione pura. Un'altra opzione è la memorizzazione automatica o la chiusura della funzione nella cache. D'altra parte: en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni

23

Un'altra cosa da considerare è che l'operatore di coalescenza non chiama il metodo get di una proprietà due volte, come fanno i ternari.

Quindi ci sono scenari in cui non dovresti usare ternary, ad esempio:

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

Se usi:

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

il getter verrà chiamato due volte e la countvariabile sarà uguale a 2 e se si utilizza:

var b = a.Prop ?? 0

la countvariabile sarà uguale a 1, come dovrebbe.


4
Questo merita più voti. Ho letto moltissime volte che ??è equivalente a ?:.
Kuba Wyrostek,

1
Punto valido su un getter chiamato due volte. Ma in questo esempio considererei un cattivo modello di design avere un getter così fuorviante da apportare effettivamente modifiche all'oggetto.
Linas,

15

Il più grande vantaggio che trovo per l' ??operatore è che puoi facilmente convertire tipi di valore annullabili in tipi non annullabili:

int? test = null;
var result = test ?? 0; // result is int, not int?

Lo uso spesso nelle query Linq:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?

Potrei essere un po 'in ritardo qui, ma quel secondo esempio lancerà se kvp == null. E in realtà Nullable<T>ha un GetValueOrDefaultmetodo che normalmente utilizzo.
CompuChip

6
KeyValuePair è un tipo di valore nel framework .NET, pertanto l'accesso a una qualsiasi delle sue proprietà non genererebbe mai un'eccezione di riferimento null. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan

9

L'ho usato ?? nella mia implementazione di IDataErrorInfo:

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

Se una singola proprietà si trova in uno stato di "errore" ottengo quell'errore, altrimenti ottengo null. Funziona davvero bene.


Interessante. Stai usando "this" come proprietà. Non l'ho mai fatto.
Armstrongest, il

Sì, fa parte di come funziona IDataErrorInfo. In genere tale sintassi è utile solo per le classi di raccolta.
Matt Hamilton,

4
Si archiviano i messaggi di errore in this["Name"], this["Address"]ecc ??
Andrew,

7

È possibile utilizzare l'operatore null coalescing per renderlo un po 'più pulito per gestire il caso in cui non è impostato un parametro opzionale:

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...

Non sarebbe bello se questa riga potesse essere scritta come arg ?= Arg.Default?
Jesse de Wit,

6

Mi piace usare l'operatore di coalescenza null per caricare in modo pigro determinate proprietà.

Un esempio molto semplice (e inventato) solo per illustrare il mio punto:

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}

Resharper suggerirà effettivamente questo come un rifrattore per un carico pigro "tradizionale".
arichards,

5

È ?? necessario o dovresti semplicemente usare l'operatore ternario (che la maggior parte ha familiarità)

In realtà, la mia esperienza è che troppe poche persone hanno familiarità con l'operatore ternario (o più correttamente, l' operatore condizionale ; ?:è "ternario" nello stesso senso che ||è binario o +è unario o binario; tuttavia capita di essere il solo operatore ternario in molte lingue), quindi almeno in quel campione limitato, la tua affermazione fallisce proprio lì.

Inoltre, come accennato in precedenza, c'è una situazione importante in cui l'operatore di coalescenza nulla è molto utile, e cioè quando l'espressione da valutare ha effetti collaterali. In tal caso, non è possibile utilizzare l'operatore condizionale senza (a) introdurre una variabile temporanea o (b) modificare la logica effettiva dell'applicazione. (b) chiaramente non è appropriato in nessuna circostanza, e sebbene sia una preferenza personale, non mi piace ingombrare l'ambito della dichiarazione con molte variabili estranee, anche se di breve durata, quindi (a) è troppo fuori in questo scenario particolare.

Naturalmente, se è necessario eseguire più controlli sul risultato, l'operatore condizionale o un insieme di ifblocchi è probabilmente lo strumento per il lavoro. Ma per semplice "se questo è nullo, usalo, altrimenti usalo", l'operatore di coalescenza null ??è perfetto.


Commento molto recente da parte mia - ma mi fa piacere vedere qualcuno che copre che un operatore ternario è un operatore con tre argomenti (di cui ora ce n'è più di uno in C #).
Steve Kidd,

5

Una cosa che ho fatto molto ultimamente è usare la coalescenza nulla per i backup as. Per esempio:

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

È anche utile per eseguire il backup di catene lunghe ?.che potrebbero non funzionare

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";

4

L'unico problema è che l'operatore null-coalesce non rileva stringhe vuote.


vale a dire

string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

PRODUZIONE:

result1 = ""

result2 = coalesced!

Attualmente sto cercando di ignorare ?? operatore per aggirare questo. Sarebbe sicuramente utile averlo integrato nel Framework.

Pensieri?


Puoi farlo con i metodi di estensione, ma sono d'accordo, sarebbe una bella aggiunta al codice e molto utile in un contesto web.
Armstrongest,

Sì, questo è uno scenario frequente ... esiste persino un metodo speciale String.IsNullOrEmpty (stringa) ...
Max Galkin,

12
"l'operatore null-coalesce non rileva stringhe vuote." Bene, è l' operatore null -coalescing, non l' operatore nullOrEmpty -coalescing. E personalmente, disprezzo mescolare valori nulli e vuoti in linguaggi che distinguono tra i due, il che rende l'interfaccia con cose che non sono abbastanza fastidiose. E sono un po 'ossessivo-compulsivo, quindi mi dà fastidio quando le lingue / implementazioni non distinguono comunque tra i due, anche se capisco il perché (come in [la maggior parte delle implementazioni di?] SQL).
JAB

3
??non può essere sovraccaricato: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx - sarebbe comunque fantastico avere un sovraccarico. Uso una combinazione: s1.Nullify() ?? s2.Nullify()dove string Nullify(this s)restituisce a nullnei casi in cui la stringa è vuota.
Kit

L'unico problema? Mi sono appena trovato a desiderare ?? = e ho trovato questo thread mentre vedevo se c'era un modo per farlo. (Situazione: il primo passaggio ha caricato i casi di eccezione, ora voglio tornare indietro e caricare i valori predefiniti in tutto ciò che non è già stato caricato.)
Loren Pechtel,

3

È ?? necessario o dovresti semplicemente usare l'operatore ternario (che la maggior parte ha familiarità)

Dovresti usare ciò che esprime meglio il tuo intento. Poiché v'è un null operatore fondono, usarlo .

D'altra parte, dal momento che è così specializzato, non penso che abbia altri usi. Avrei preferito un sovraccarico appropriato ||dell'operatore, come fanno altre lingue. Questo sarebbe più parsimonioso nella progettazione del linguaggio. Ma bene …


3

Freddo! Contami come qualcuno che non sapeva dell'operatore a coalescenza nulla - è roba piuttosto ingegnosa.

Trovo molto più facile da leggere rispetto all'operatore ternario.

Il primo posto che mi viene in mente dove potrei usarlo è quello di mantenere tutti i miei parametri predefiniti in un unico posto.

public void someMethod( object parm2, ArrayList parm3 )
{ 
  someMethod( null, parm2, parm3 );
}
public void someMethod( string parm1, ArrayList parm3 )
{
  someMethod( parm1, null, parm3 );
}
public void someMethod( string parm1, object parm2, )
{
  someMethod( parm1, parm2, null );
}
public void someMethod( string parm1 )
{
  someMethod( parm1, null, null );
}
public void someMethod( object parm2 )
{
  someMethod( null, parm2, null );
}
public void someMethod( ArrayList parm3 )
{
  someMethod( null, null, parm3 );
}
public void someMethod( string parm1, object parm2, ArrayList parm3 )
{
  // Set your default parameters here rather than scattered through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}

2

Un po 'uno strano caso d'uso, ma avevo un metodo in cui un IDisposableoggetto è potenzialmente passato come arg (e quindi eliminato dal genitore), ma potrebbe anche essere nullo (e quindi dovrebbe essere creato e disposto nel metodo locale)

Per usarlo, il codice o sembrava

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

Ma con una coalescenza nulla diventa molto più ordinato

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

0

Ho usato così:

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
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.