Ha senso usare "as" invece di un cast anche se non è presente un controllo null? [chiuso]


351

Nei blog di sviluppo, esempi di codice online e (di recente) anche un libro, continuo a imbattermi in codice come questo:

var y = x as T;
y.SomeMethod();

o, peggio ancora:

(x as T).SomeMethod();

Questo non ha senso per me. Se si è certi che xè di tipo T, è necessario utilizzare un cast diretta: (T)x. Se non si è sicuri, è possibile utilizzare asma è necessario verificare nullprima di eseguire alcune operazioni. Tutto quello che fa il codice sopra è di trasformare un (utile) InvalidCastExceptionin un (inutile) NullReferenceException.

Sono l'unico a pensare che si tratti di un palese abuso della asparola chiave? O mi sono perso qualcosa di ovvio e lo schema sopra ha davvero senso?


56
Sarebbe più divertente vedere (bacio come S) .SteveIsSuchA (); Ma sono d'accordo, è un abuso.
SwDevMan81,

5
È molto più bello della scrittura ((T)x).SomeMethod(), no? ;) (sto scherzando, hai ragione ovviamente!)
Lucero,

8
@P Papà Non sono d'accordo, domanda perfettamente valida (questo schema di codice ha davvero senso) e molto utile. +1 alla domanda e un cipiglio a chiunque voti per chiudere.
MarkJ,

9
Lucerno ha ragione, questo schema di codifica è indotto cercando di evitare le parentesi. Incurabile dopo essere stato esposto a Lisp.
Hans Passant,

13
Codice ottimizzato (f as T).SomeMethod()
:;

Risposte:


252

La tua comprensione è vera. Mi sembra di provare a micro-ottimizzare per me. Dovresti usare un cast normale quando sei sicuro del tipo. Oltre a generare un'eccezione più sensata, fallisce anche velocemente. Se ti sbagli su tua ipotesi sul tipo, il vostro programma fallirà immediatamente e sarete in grado di vedere la causa del fallimento subito piuttosto che aspettare una NullReferenceExceptiono ArgumentNullExceptiono anche un qualche errore logico in futuro. In generale, asun'espressione che non è seguita da un nullsegno di spunta da qualche parte è un odore di codice.

D'altra parte, se non sei sicuro del cast e ti aspetti che fallisca, dovresti usare asinvece di un cast normale avvolto in un try-catchblocco. Inoltre, assi consiglia l' uso di un controllo del tipo seguito da un cast. Invece di:

if (x is SomeType)
   ((SomeType)x).SomeMethod();

che genera isinstun'istruzione per la isparola chiave e castclassun'istruzione per il cast (eseguendo effettivamente il cast due volte), è necessario utilizzare:

var v = x as SomeType;
if (v != null)
    v.SomeMethod();

Questo genera solo isinstun'istruzione. Il primo metodo presenta un potenziale difetto nelle applicazioni multithread poiché una condizione di competizione potrebbe causare il cambio di tipo della variabile dopo che il iscontrollo ha avuto esito positivo e si è verificato un errore nella linea di lancio. Quest'ultimo metodo non è soggetto a questo errore.


La seguente soluzione non è consigliata per l'uso nel codice di produzione. Se odi davvero un costrutto così fondamentale in C #, potresti prendere in considerazione il passaggio a VB o qualche altra lingua.

Nel caso in cui uno odia disperatamente la sintassi del cast, può scrivere un metodo di estensione per imitare il cast:

public static T To<T>(this object o) { // Name it as you like: As, Cast, To, ...
    return (T)o;
}

e usa una sintassi [?] pulita:

obj.To<SomeType>().SomeMethod()

5
Penso che le condizioni di gara siano irrilevanti. Se riscontri questo problema, il tuo codice non è thread-safe e ci sono modi più affidabili per risolverlo che usando la parola chiave "as". +1 per il resto della risposta.
RMorrisey,

10
@RMorrisey: ho almeno un esempio in mente: supponi di avere un cacheoggetto su cui un altro thread tenta di invalidarlo impostandolo su null. In scenari senza blocco, questo tipo di cose può sorgere.
Mehrdad Afshari,

10
è + cast è abbastanza per innescare un "Non gettare inutilmente" avvertimento da FxCop: msdn.microsoft.com/en-us/library/ms182271.aspx Che dovrebbe essere una ragione sufficiente per evitare il costrutto.
David Schmitt,

2
Dovresti evitare di attivare metodi di estensione Object. L'uso del metodo su un tipo di valore comporterà l'inscatolamento inutile.
MgSam,

2
@MgSam Ovviamente, un tale caso d'uso non ha senso per il Tometodo qui, poiché converte solo attraverso la gerarchia dell'ereditarietà, che per i tipi di valore comporta comunque la boxe. Naturalmente, l'intera idea è più teorica che seria.
Mehrdad Afshari,

42

IMHO, ha assenso solo se combinato con un nullcontrollo:

var y = x as T;
if (y != null)
    y.SomeMethod();

40

L'uso di "as" non applica le conversioni definite dall'utente mentre il cast le utilizzerà laddove appropriato. Questa può essere una differenza importante in alcuni casi.


5
Questo è importante da ricordare. Eric Lippert lo analizza
P Daddy,

5
Bel commento, P! Se il tuo codice dipende da questa distinzione, però, direi che c'è una sessione di debug a tarda notte in futuro.
TrueWill,

36

Ho scritto un po 'su questo qui:

http://blogs.msdn.com/ericlippert/archive/2009/10/08/what-s-the-difference-between-as-and-cast-operators.aspx

Capisco il tuo punto. E sono d'accordo con la sua spinta: che un operatore del cast comunica "Sono sicuro che questo oggetto può essere convertito in quel tipo e sono disposto a rischiare un'eccezione se sbaglio", mentre un operatore "come" comunica "Non sono sicuro che questo oggetto possa essere convertito in quel tipo; dammi un null se sbaglio".

Tuttavia, c'è una sottile differenza. (x come T). Qualsiasi cosa () comunica "Non so solo che x può essere convertito in una T, ma inoltre che ciò comporta solo conversioni di riferimento o unboxing, e inoltre che x non è nullo". Ciò comunica informazioni diverse da ((T) x). Qualunque cosa (), e forse è quello che intende l'autore del codice.


1
Non sono d'accordo con la tua difesa speculativa dell'autore del codice nella tua ultima frase. comunica ((T)x).Whatever() inoltre che xnon è [destinato ad essere] nullo e dubito fortemente che un autore in genere si preoccuperebbe se la conversione si Tverifica solo con conversioni di riferimento o unboxing o se richiede una conversione definita dall'utente o che cambia la rappresentazione. Dopotutto, se lo definisco public static explicit operator Foo(Bar b){}, è chiaramente il mio intento Barda considerare compatibile Foo. È raro che vorrei evitare questa conversione.
P Papà,

4
Bene, forse la maggior parte degli autori di codice non farebbe questa sottile distinzione. Personalmente potrei esserlo, ma se lo fossi, aggiungerei un commento in tal senso.
Eric Lippert,

16

Ho visto spesso riferimenti a questo articolo fuorviante come prova che "as" è più veloce del casting.

Uno degli aspetti più ovvi e fuorvianti di questo articolo è il grafico, che non indica ciò che viene misurato: sospetto che stia misurando i calchi falliti (dove "as" è ovviamente molto più veloce in quanto non viene generata alcuna eccezione).

Se ti prendi il tempo per fare le misurazioni, vedrai che il casting è, come ti aspetteresti, più veloce di "come" quando il cast avrà successo.

Sospetto che questo possa essere uno dei motivi per l'uso di "cult cult" come parola chiave anziché come cast.


2
Grazie per il link, è molto interessante. Da quanto ho capito l'articolo, si fa confrontare il caso non fa eccezione. Tuttavia, l'articolo è stato scritto per .net 1.1 e i commenti sottolineano che questo è cambiato in .net 2.0: le prestazioni sono ora quasi uguali, con il cast del prefisso anche leggermente più veloce.
Heinzi,

1
L'articolo implica che sta confrontando il caso di non eccezione, ma ho fatto alcuni test molto tempo fa e non sono stato in grado di riprodurre i risultati dichiarati, anche con .NET 1.x. E poiché l'articolo non fornisce il codice utilizzato per eseguire il benchmark, è impossibile dire cosa viene confrontato.
Joe,

"Cargo cult" - perfetto. Dai un'occhiata a "Cargo Cult Science Richard Feynman" per le informazioni complete.
Bob Denny,

11

Il cast diretto richiede una coppia di parentesi in più rispetto alla asparola chiave. Quindi, anche nel caso in cui tu sia sicuro al 100% del tipo, riduce l'ingombro visivo.

Concordato sull'eccezione, però. Ma almeno per me, la maggior parte degli usi di si asriduce per controllare in nullseguito, che trovo più bello che prendere un'eccezione.


8

Il 99% delle volte quando uso "as" è quando non sono sicuro di quale sia il tipo di oggetto reale

var x = obj as T;
if(x != null){
 //x was type T!
}

e non voglio catturare esplicite eccezioni di cast né fare cast due volte, usando "is":

//I don't like this
if(obj is T){
  var x = (T)obj; 
}

8
Hai appena descritto il caso d'uso corretto per as. Qual è l'altro 1%?
P Papà,

In un refuso? =) Intendevo il 99% delle volte che utilizzo questo esatto frammento di codice, mentre a volte posso usare "as" in una chiamata di metodo o in qualche altro posto.
Max Galkin,

D'oh, e in che modo è meno utile della seconda risposta popolare ???
Max Galkin,

2
+1 Sono d'accordo nel dire che questo è prezioso quanto la risposta di Rubens Farias - speriamo che la gente venga qui e questo sarà un esempio utile
Ruben Bartelink,

8

È solo perché alla gente piace come appare, è molto leggibile.

Ammettiamolo: l'operatore di casting / conversione in linguaggi simil-C è piuttosto terribile, in termini di leggibilità. Mi piacerebbe se C # adottasse la sintassi Javascript di:

object o = 1;
int i = int(o);

O definire un tooperatore, l'equivalente di casting di as:

object o = 1;
int i = o to int;

Solo per questo, la sintassi JavaScript che menzioni è consentita anche in C ++.
P Daddy,

@PDaddy: Non è però una sintassi alternativa compatibile diretta al 100% e non è intesa come tale (operatore X contro costruttore di conversione)
Ruben Bartelink,

Preferirei usare la sintassi C ++ di dynamic_cast<>()(e simili). Stai facendo qualcosa di brutto, dovrebbe sembrare brutto.
Tom Hawtin: affronta il

5

Alla gente piace ascosì tanto perché li fa sentire al sicuro da eccezioni ... Come garanzia su una scatola. Un ragazzo offre una garanzia di fantasia sulla scatola perché vuole che tu ti senta tutto caldo e tostato dentro. Credi di aver messo quella scatoletta sotto il cuscino di notte, la Fata della Garanzia potrebbe scendere e lasciare un quarto, vero Ted?

Di nuovo sull'argomento ... quando si utilizza un cast diretto, esiste la possibilità di un'eccezione di cast non valida. Quindi le persone si applicano ascome una soluzione generale per tutte le loro esigenze di casting perché as(di per sé) non genererà mai un'eccezione. Ma la cosa divertente di questo, è nell'esempio che hai dato, (x as T).SomeMethod();stai scambiando un'eccezione cast non valida con un'eccezione di riferimento null. Il che offusca il vero problema quando vedi l'eccezione.

In genere non uso astroppo. Preferisco il istest perché a me sembra più leggibile e ha più senso quindi provare un cast e verificare la presenza di null.


2
"Preferisco il test is" - "is" seguito da un cast è ovviamente più lento di "as" seguito da un test per null (proprio come "IDictionary.ContainsKey" seguito da dereferencing usando l'indicizzatore è più lento di "IDictionary.TryGetValue "). Ma se lo trovi più leggibile, senza dubbio la differenza è raramente significativa.
Joe,

L'importante affermazione nella parte centrale è come le persone si applicano ascome una soluzione generale perché le fa sentire sicure.
Bob,

5

Questo deve essere uno dei miei migliori problemi .

Il D&E di Stroustrup e / o qualche post sul blog che non riesco a trovare in questo momento discute la nozione di tooperatore che affronterebbe il punto sollevato da https://stackoverflow.com/users/73070/johannes-rossel (cioè la stessa sintassi asma con la DirectCastsemantica ).

Il motivo per cui questo non è stato implementato è perché un cast dovrebbe causare dolore ed essere brutto in modo da essere respinto dall'usarlo.

Peccato che i programmatori "intelligenti" (spesso autori di libri (Juval Lowy IIRC)) si aggirino abusando asin questo modo (C ++ non offre un as, probabilmente per questo motivo).

Anche VB ha più coerenza nell'avere una sintassi uniforme che ti costringe a scegliere un TryCasto DirectCaste prendere una decisione !


+1. Probabilmente intendevi DirectCast comportamento , non sintassi .
Heinzi,

@Heinzi: Ta per +1. Buon punto. Ha deciso di essere una smartarse e utilizzare semanticsinvece: P
Ruben Bartelink,

Dato che C # non ha alcuna pretesa di compatibilità con C, C ++ o Java, mi trovo infastidito da alcune delle cose che prende in prestito da quelle lingue. Va oltre "So che questa è una X" e "So che questa non è una X, ma può essere rappresentata come una", a "So che questa non è una X e potrebbe non essere realmente rappresentabile come una , ma dammi comunque una X ". Potrei vedere l'utilità di un double-to-intcast che fallirebbe se doublenon rappresentasse un valore esatto che potrebbe rientrare in un Int32, ma avere un (int)-1.5rendimento -1 è semplicemente brutto.
supercat,

@supercat Sì, ma il design del linguaggio non è facile anche se, come tutti sappiamo, guarda il set di compromessi coinvolti in nullable di C #. L'unico antidoto conosciuto è la lettura regolare di C # nelle edizioni Depth man mano che arrivano :) Per fortuna sono più interessato a capire le sfumature di F # in questi giorni ed è molto più sano su molte di queste questioni.
Ruben Bartelink,

@RubenBartelink: Non sono del tutto chiaro quali problemi esatti avrebbero dovuto risolvere i tipi nullable, ma penso che nella maggior parte dei casi sarebbe stato meglio avere un MaybeValid<T>con due campi pubblici IsValide Valuequale codice avrebbe potuto fare a suo piacimento. Ciò avrebbe permesso ad es MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }. Non solo questo risparmierebbe almeno due operazioni di copia rispetto Nullable<T>, ma potrebbe valere anche con qualsiasi tipo, Tnon solo le classi.
supercat,

2

Credo che la asparola chiave possa essere pensata come una versione dall'aspetto più elegante del dynamic_castC ++.


Direi che un cast diretto in C # è più simile dynamic_casta C ++.
P Papà,

penso che il cast etero in C # sia più equivalente al static_cast in C ++.
Andrew Garrison,

2
@Ruben Bartelink: restituisce null solo con puntatori. Con riferimenti, che dovresti usare quando possibile, genera std::bad_cast.
P Daddy,

1
@Andrew Garrison: static_castnon esegue alcun controllo del tipo di runtime. Non esiste cast simile a questo in C #.
P Daddy,

Purtroppo, non sapevo che avresti potuto usare i cast anche sui riferimenti, dato che li ho sempre e solo usati sui puntatori, ma P Daddy ha assolutamente ragione!
Andrew Garrison,

1

Probabilmente è più popolare senza motivo tecnico, ma solo perché è più facile da leggere e più intuitivo. (Non dire che rende meglio solo provare a rispondere alla domanda)


1

Un motivo per usare "as":

T t = obj as T;
 //some other thread changes obj to another type...
if (t != null) action(t); //still works

Invece di (codice errato):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}

2
Se hai delle condizioni di gara questo brutto problema hai problemi più grandi (ma concorda che è un bel campione da andare con gli altri, quindi +1
Ruben Bartelink,

-1 poiché ciò perpetua un errore. Se altri thread possono cambiare il tipo di obj, allora hai ancora problemi. L'affermazione "// funziona ancora" non è molto probabile che sia vera, poiché t verrà utilizzato come puntatore a T, ma punta alla memoria che non è più un T. Né la soluzione funzionerà quando l'altro thread cambia il tipo di obj mentre è in corso l'azione (t).
Stephen C. Steel,

5
@Stephen C. Steel: sembri piuttosto confuso. Cambiare il tipo di objsignificherebbe cambiare la objvariabile stessa per contenere un riferimento a un altro oggetto. Non altererebbe il contenuto della memoria in cui risiede l'oggetto originariamente indicato da obj. Questo oggetto originale rimarrebbe invariato e la tvariabile conterrebbe comunque un riferimento ad esso.
P Papà,


1
@P Papà - Penso che tu abbia ragione, e mi sbagliavo: se obj fosse rimbalzato da un oggetto T a un oggetto T2, allora t starebbe ancora puntando al vecchio oggetto T. Poiché t fa ancora riferimento al vecchio oggetto, non può essere garbage collection, quindi il vecchio oggetto T rimarrà valido. I miei circuiti di rilevamento delle condizioni di gara sono stati addestrati su C ++, in cui un codice simile utilizzando dynamic_cast sarebbe un potenziale problema.
Stephen C. Steel,
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.