Operatore di coalescenza immobiliare per C #


9

L'operatore a coalescenza nulla in c # consente di abbreviare il codice

  if (_mywidget == null)
     return new Widget();
  else
     return _mywidget;

Giù verso:

  return _mywidget ?? new Widget();

Continuo a scoprire che un operatore utile che vorrei avere in C # sarebbe quello che ti avrebbe permesso di restituire una proprietà di un oggetto, o qualche altro valore se l'oggetto è nullo. Quindi vorrei sostituirlo

  if (_mywidget == null)
     return 5;
  else
     return _mywidget.Length;

Con:

  return _mywidget.Length ??! 5;

Non posso fare a meno di pensare che ci debba essere una ragione per cui questo operatore non esiste. È un odore di codice? C'è un modo migliore per scrivere questo? (Sono a conoscenza del modello di oggetti null ma sembra eccessivo usarlo per sostituire queste quattro righe di codice.)


1
L'operatore condizionale sarebbe sufficiente qui?
Anon.

1
Qualcuno ha scritto qualcosa che ti consente di fare qualcosa del genere: string location = employee.office.address.location ?? "Sconosciuto"; . Questa impostazione imposta la posizione su "Sconosciuto" quando uno degli oggetti ( impiegato , ufficio , indirizzo o posizione ) è nullo. Sfortunatamente, non ricordo chi l'abbia scritto o dove l'abbia pubblicato. Se lo trovo di nuovo, lo posterò qui!
Kristof Claes,

1
Questa domanda otterrebbe molta più trazione su StackOverflow.
Giobbe

2
??!è un operatore in C ++. :-)
James McNellis il

3
Ciao 2011, ho appena chiamato per dire che c # sta ottenendo un operatore di navigazione sicuro
Nathan Cooper,

Risposte:


5

Voglio moltissimo una funzionalità del linguaggio C # che mi permetta di eseguire in modo sicuro x.Prop.SomeOtherProp.ThirdProp senza dover controllare ognuno di questi per null. In una base di codice in cui ho dovuto fare un sacco di "attraversamenti di proprietà profonde", ho scritto un metodo di estensione chiamato Navigate che ha ingoiato NullReferenceExceptions e invece ha restituito un valore predefinito. Quindi potrei fare qualcosa del genere:

var propVal = myThing.Navigate(x => x.PropOne.PropTwo.PropThree.PropFour, defaultValue);

L'aspetto negativo di questo è che ha un odore di codice diverso: eccezioni ingerite. Se volessi fare qualcosa del genere "giusto", potresti prendere la lamdba come espressione e modificare l'espressione al volo per aggiungere i controlli null intorno a ciascun accessorio di proprietà. Sono andato per "veloce e sporco" e non l'ho implementato in questo modo "migliore". Ma forse quando avrò dei cicli di pensiero di riserva, lo rivisiterò e lo pubblicherò da qualche parte.

Per rispondere in modo più diretto alla tua domanda, immagino che il motivo per cui questa non è una funzione sia dovuto al fatto che il costo dell'implementazione della funzione supera il vantaggio derivante dall'avere la funzione. Il team di C # deve scegliere le caratteristiche su cui concentrarsi e questa non è ancora arrivata all'inizio. Solo una supposizione, anche se non ho informazioni privilegiate.


1
Esattamente quello che pensavo, ma immagino che le prestazioni del modo sicuro sarebbero piuttosto cattive (a causa dei numerosi Expression.Compile ()) ... Peccato che non sia implementato in C # (qualcosa come Obj.?PropA.?Prop1)
Guillaume86

x.PropOne.PropTwo.PropThree.PropFour è una scelta di design scadente in quanto viola la Legge di Demetra. Riprogetta i tuoi metodi / classi in modo che non sia necessario utilizzarli.
Justin Shield,

Evitare irrimediabilmente un profondo accesso alla proprietà alla luce della Legge di Demetra è pericoloso quanto normalizzare inconsapevolmente un database. Puoi andare troppo lontano.
Mir,

3
In C # 6.0 questo è possibile utilizzando l'operatore Null-condizionale (aka l'operatore Elvis) msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan

15

Credo che sarai in grado di farlo con C # 6 abbastanza semplicemente ora:

return _mywidget?.Length ?? 5;

Nota l'operatore null-conditional ?.sul lato sinistro. _mywidget?.Lengthrestituisce null se _mywidgetè null.

Tuttavia, un semplice operatore ternario potrebbe essere più facile da leggere, come altri hanno suggerito.


9

È davvero solo una speculazione sul perché non l'hanno fatto. Dopo tutto, non ci credo ?? era nella prima versione di C #.

Ad ogni modo, vorrei semplicemente usare l'operatore condizionale e chiamarlo un giorno:

return (_mywidget != null) ? _mywidget.Length : 5;

Il ?? è solo una scorciatoia per l'operatore condizionale.


5
Personalmente, questo è altrettanto brutto (imho) che avere un operatore per farlo in primo luogo. Vuoi qualcosa da un oggetto ... quell'oggetto potrebbe non essere lì ... quindi forniamo 5come risposta nel caso in cui non ci fosse. Devi esaminare il tuo progetto prima di iniziare a chiedere operatori speciali.
Moo-Juice,

1
@ Moo-Juice Sto solo immaginando la persona che mantiene questo codice, 'Perché questo mi dà 5? Hrmmm. Che cosa?!'
msarchet,

4

Sembra proprio l'operatore ternario:

return (_mywidget != NULL) ? _mywidget : new Widget();

3

Personalmente, penso che sia dovuto alla leggibilità. Nel primo esempio, si ??traduce abbastanza bene come "Ci sei ?? No, facciamolo".

Nel secondo esempio, ??!è chiaramente WTF e non significa nulla per il programmatore .... l'oggetto non esiste, quindi non riesco ad accedere alla proprietà, quindi restituirò 5. Cosa significa? Che cos'è 5? Come giungiamo alla conclusione che 5 è un buon valore?

In breve, il primo esempio ha un senso ... non c'è, nuovo . Nel secondo, è aperto al dibattito.


2
Bene, devi ignorare il fatto che ??! non esiste. Immagina se ??! era comune e, se usato, prendere decisioni basate su quello.
whatsisname

3
Questo mi ricorda il cosiddetto "operatore WTF" in C ++ (funziona davvero:. (foo() != ERROR)??!??! cerr << "Error occurred" << endl;)
Tamás Szelei,

@whatsisname, era più una riflessione sul fatto che era appropriato per la decisione presa. Creare un oggetto perché non c'era non ha senso. Assegnare un valore arbituario che non significa nulla per il lettore perché non si può ottenere una proprietà di qualcosa, è davvero uno scenario WTF.
Moo-Juice,

Penso che vieni coinvolto nel mio esempio. Forse 0 è meglio di 5. Forse la proprietà è un oggetto, quindi se _mywidget è null, si desidera restituire un nuovo foodle (). Non sono completamente d'accordo ?? è più leggibile di ??! comunque :)
Ben Fulton il

@Ben, anche se concordo sul fatto che concentrarsi sull'operatore stesso non si presti troppo alla tua domanda, stavo disegnando un parallelo con la percezione del programmatore e quella arbitraria 5. Penso davvero che ci sia più di un problema che devi fare qualcosa del genere, mentre nel tuo primo esempio, la necessità è abbastanza ovvia, specialmente in cose come i gestori di cache.
Moo-Juice,

2

Penso che le persone siano troppo coinvolte nel "5" che stai tornando ... forse 0 sarebbe stato meglio :)

Comunque, penso che il problema sia che in ??!realtà non è un operatore autonomo. Considera cosa significherebbe:

var s = myString ??! "";

In tal caso, non ha senso: ha senso solo se l'operando di sinistra è un accessore di proprietà. O che dire di questo:

var s = Foo(myWidget.Length) ??! 0;

C'è un accessor di proprietà lì dentro, ma ancora non credo abbia senso (se lo myWidgetè null, significa che non chiamiamo Foo()affatto?), O è solo un errore?

Penso che il problema sia che non si adatta alla lingua in modo naturale ??.


1

Qualcuno ha creato una classe di utilità che farebbe questo per te. Ma non riesco a trovarlo. Quello che ho trovato è stato qualcosa di simile nei forum MSDN (guarda la seconda risposta).

Con un po 'di lavoro, puoi estenderlo per valutare le chiamate di metodo e altre espressioni che l'esempio non supporta. È inoltre possibile estenderlo per accettare un valore predefinito.


1

Capisco da dove vieni. Sembra che tutti si stiano avvolgendo attorno all'asse con l'esempio che hai fornito. Mentre sono d'accordo sul fatto che i numeri magici siano una cattiva idea, ci sarebbe un uso per l'equivalente di un operatore null coallescente per determinate proprietà.

Ad esempio, hai oggetti associati a una mappa e vuoi che siano alla quota corretta. Le mappe DTED possono avere buchi nei loro dati, quindi è possibile avere un valore nullo, nonché valori nell'intervallo tra -100 e ~ 8900 metri. Potresti voler qualcosa sulla falsariga di:

mapObject.Altitude = mapObject.Coordinates.DtedAltitude ?? DEFAULT_ALTITUDE;

L'operatore null coallescing in questo caso popolerebbe l'altitudine predefinita se le Coordinate non erano ancora impostate o l'oggetto Coordinate non poteva caricare i dati DTED per quella posizione.

Lo considero molto prezioso, ma la mia speculazione sul perché non è stato fatto è limitata alla complessità del compilatore. Ci possono essere alcuni casi in cui il comportamento non sarebbe così prevedibile.


1

Quando non ho a che fare con un annidamento profondo l'ho usato (prima che l'operatore di coalescenza nulla introdotto in C # 6). Per me è abbastanza chiaro, ma forse è perché ci sono abituato, altre persone potrebbero trovarlo confuso.

return ( _mywidget ?? new MyWidget() {length = defaultLength}).Length;

Naturalmente, ciò non è sempre applicabile, poiché a volte la proprietà a cui è necessario accedere non può essere impostata direttamente o quando la costruzione dell'oggetto stesso è costosa, tra le altre ragioni.

Un'altra alternativa è utilizzare il modello NullObject . Definisci una singola istanza statica della classe con valori predefiniti sensibili e la usi invece.

return ( _mywidget ?? myWidget.NullInstance).Length;

0

I numeri magici di solito hanno un cattivo odore. Se il 5 è un numero arbitrario, è meglio precisarlo in modo che sia documentato più chiaramente. Un'eccezione secondo me sarebbe se si dispone di una raccolta di valori che agiscono come valori predefiniti in un determinato contesto.

Se si dispone di un set di valori predefiniti per un oggetto, è possibile sottoclassarlo per creare un'istanza singleton predefinita.

Qualcosa del tipo: return (myobj ?? default_obj) .Length


0

La proprietà length di solito è un tipo di valore, quindi non può essere nulla, quindi l'operatore a coalescenza nulla non ha senso qui.

Ma puoi farlo con una proprietà che è un tipo di riferimento, ad esempio: var window = App.Current.MainWindow ?? new Window();


L'esempio sta cercando di considerare cosa accadrebbe nella tua situazione se Current fosse null. Il tuo esempio andrebbe in crash ... ma sarebbe utile avere un operatore che può annullare la coalescenza in qualsiasi punto della catena della direzione errata.
Mir,

-1

Ho già visto qualcuno con un'idea simile, ma la maggior parte delle volte vorresti anche assegnare il nuovo valore nel campo null, qualcosa del tipo:

return _obj ?? (_obj = new Class());

quindi l'idea era quella di combinare ??e il =compito in:

return _obj ??= new Class();

Penso che questo abbia più senso che usare un punto esclamativo.

Questa idea non è mia ma mi piace davvero :)


1
Sei una vittima del povero esempio? Il tuo esempio può essere abbreviato in quanto return _obj ?? new Class();non è necessario per il ??=qui.
Matt Ellen,

@Matt Ellen hai sbagliato. L'incarico è previsto . Sto suggerendo un'alternativa diversa. Non è esattamente come quello richiesto dall'OP, ma credo che sarà altrettanto utile.
Chakrit,

2
perché vuoi l'assegnazione se stai per restituire un valore?
Matt Ellen,

lazy-inizializzazione?
Chakrit,
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.