Casting vs utilizzando la parola chiave "as" nel CLR


387

Durante la programmazione delle interfacce, ho scoperto che sto facendo molto casting o conversione del tipo di oggetto.

C'è una differenza tra questi due metodi di conversione? In tal caso, esiste una differenza di costo o in che modo influisce sul mio programma?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

Inoltre, qual è "in generale" il metodo preferito?


Potresti aggiungere un piccolo esempio del perché stai usando i cast in primo luogo alla domanda, o forse ne avvii uno nuovo? Sono un po 'interessato al perché avresti bisogno del cast solo per i test unitari. Penso che sia al di fuori del campo di applicazione di questa domanda.
Erik van Brakel,

2
Probabilmente posso cambiare il mio test unitario per evitare questo bisogno di questo. Fondamentalmente si riduce al fatto che ho una proprietà sul mio oggetto concreto che non è nell'interfaccia. Devo impostare quella proprietà, ma nella vita reale quella proprietà sarebbe stata impostata con altri mezzi. Questo risponde alla tua domanda?
Frank V,

Come Patrik Hagne sottolinea acutamente qui di seguito, vi È una differenza.
Neil,

Risposte:


519

La risposta sotto la riga è stata scritta nel 2008.

C # 7 ha introdotto la corrispondenza dei modelli, che ha ampiamente sostituito l' asoperatore, come ora puoi scrivere:

if (randomObject is TargetType tt)
{
    // Use tt here
}

Si noti che ttè ancora nell'ambito dopo questo, ma non definitivamente assegnato. (Si è definitivamente assegnato all'interno del ifcorpo.) Questo è un po 'fastidioso in alcuni casi, quindi se vi interessa davvero di introdurre il minor numero di variabili possibili in ogni ambito, si potrebbe ancora voglia di usare isseguito da un cast.


Non credo che nessuna delle risposte finora (al momento di iniziare questa risposta!) Abbia davvero spiegato dove vale la pena usare quale.

  • Non farlo:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }

    Questo non solo controlla due volte, ma può controllare cose diverse, se si randomObjecttratta di un campo anziché di una variabile locale. È possibile che l '"if" passi, ma poi il cast fallisce, se un altro thread cambia il valore randomObjecttra i due.

  • Se randomObjectdavvero dovrebbe essere un'istanza di TargetType, cioè se non lo è, ciò significa che c'è un bug, quindi il casting è la soluzione giusta. Ciò genera immediatamente un'eccezione, il che significa che non viene più eseguito alcun lavoro in base a presupposti errati e l'eccezione mostra correttamente il tipo di bug.

    // This will throw an exception if randomObject is non-null and
    // refers to an object of an incompatible type. The cast is
    // the best code if that's the behaviour you want.
    TargetType convertedRandomObject = (TargetType) randomObject;
  • Se randomObject potrebbe essere un'istanza di TargetTypeed TargetTypeè un tipo di riferimento, utilizzare il codice in questo modo:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
  • Se randomObject potrebbe essere un'istanza di TargetTypeed TargetTypeè un tipo di valore, allora non possiamo usare ascon TargetTypese stesso, ma possiamo usare un tipo nullable:

    TargetType? convertedRandomObject = randomObject as TargetType?;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject.Value
    }

    (Nota: attualmente questo è in realtà più lento di + cast . Penso che sia più elegante e coerente, ma ci andiamo.)

  • Se davvero non hai bisogno del valore convertito, ma devi solo sapere se si tratta di un'istanza di TargetType, l' isoperatore è tuo amico. In questo caso, non importa se TargetType è un tipo di riferimento o un tipo di valore.

  • Potrebbero esserci altri casi che coinvolgono i generici dove isè utile (perché potresti non sapere se T è un tipo di riferimento o meno, quindi non puoi usarli come) ma sono relativamente oscuri.

  • Ho quasi sicuramente usato isper il caso del tipo di valore prima d'ora, non avendo pensato di usare un tipo nullable e asinsieme :)


EDIT: Nota che nessuna delle precedenti parla di prestazioni, a parte il caso del tipo di valore, in cui ho notato che l'annullamento del boxing a un tipo di valore nulla è in realtà più lento, ma coerente.

Secondo la risposta di Naasking, is-and-cast o is-and-as sono entrambi veloci come as-and-null-check con i moderni JIT, come mostrato dal codice seguente:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Sul mio laptop, tutti questi eseguono in circa 60ms. Due cose da notare:

  • Non c'è alcuna differenza significativa tra loro. (In effetti, ci sono situazioni in cui as-plus-null-check è decisamente più lento. Il codice sopra in realtà rende il controllo del tipo semplice perché è per una classe sigillata; se stai controllando un'interfaccia, i consigli di bilancio leggermente a favore di as-plus-null-check.)
  • Sono tutti follemente veloci. Questo semplicemente non sarà il collo di bottiglia nel tuo codice a meno che tu non abbia intenzione di fare nulla con i valori in seguito.

Quindi non preoccupiamoci delle prestazioni. Preoccupiamoci di correttezza e coerenza.

Ritengo che is-and-cast (o is-and-as) siano entrambi non sicuri quando si tratta di variabili, poiché il tipo di valore a cui si riferisce può cambiare a causa di un altro thread tra il test e il cast. Sarebbe una situazione piuttosto rara, ma preferirei avere una convenzione che posso usare in modo coerente.

Ritengo inoltre che il controllo as-then-null dia una migliore separazione delle preoccupazioni. Abbiamo un'istruzione che tenta una conversione e quindi un'istruzione che utilizza il risultato. Is-and-cast o is-and-as esegue un test e quindi un altro tentativo di convertire il valore.

Per dirla in altro modo, qualcuno avrebbe mai scritto:

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

È una specie di cosa sta facendo il cast - sebbene ovviamente in un modo piuttosto economico.


7
Ecco il costo di is / as / casting in termini di IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
plinto

3
Nel caso, se targetObject potrebbe essere di tipo target, perché l'uso di "is" e la combinazione di cast considerati una cattiva pratica? Voglio dire, genera un codice più lento, ma in questo caso le intenzioni sono più chiare del cast di AS, come "Fai qualcosa se targetObject è targetType", invece di "Fai qualcosa se targetObject è null", inoltre la clausola AS creerà una variabile non necessaria fuori portata IF.
Valera Kolupaev,

2
@Valera: Aspetti positivi, anche se suggerirei che il test as / null è sufficientemente idiomatico che l'intenzione dovrebbe essere chiara a quasi tutti gli sviluppatori C #. Non mi piace la duplicazione coinvolta nel cast di is +, personalmente. Vorrei davvero una sorta di costrutto "as-if" che compie entrambe le azioni in una. Vanno insieme così spesso ...
Jon Skeet,

2
@Jon Skeet: mi dispiace per il mio ritardo.È e Cast: 2135, Is And As: 2145, As E null check: 1961, specifiche: OS: Windows Seven, CPU: i5-520M, 4 GB di RAM DDR3 1033, benchmark su array di 128.000.000 di articoli.
Behrooz,

2
Con C # 7 puoi fare: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}o usare switch/ case vedere documenti
WerWet,

72

"as" restituirà NULL se non è possibile eseguire il cast.

il casting prima solleverà un'eccezione.

Per le prestazioni, sollevare un'eccezione è generalmente più costoso nel tempo.


3
L'aumento delle eccezioni è più costoso, ma se sai che l'oggetto può essere lanciato correttamente, poiché richiede più tempo a causa del controllo di sicurezza (vedi la risposta di Anton). Tuttavia, il costo del controllo di sicurezza è, credo, abbastanza piccolo.

17
Il costo del potenziale aumento di un'eccezione è un fattore da considerare, ma spesso è la progettazione corretta.
Jeffrey L Whitledge,

@panesofglass - Per i tipi di riferimento, la compatibilità di conversione verrà sempre verificata in fase di esecuzione sia per as che per cast, in modo tale fattore non distinguerà tra le due opzioni. (Se così non fosse, il cast non potrebbe sollevare un'eccezione.)
Jeffrey L Whitledge,

4
@Frank - Se ti viene richiesto di utilizzare una raccolta pre-generica, ad esempio, e un metodo nella tua API richiede un elenco di dipendenti e alcuni joker invece passano un elenco di prodotti, un'eccezione cast non valida potrebbe essere appropriata per segnalare la violazione dei requisiti di interfaccia.
Jeffrey L Whitledge,

27

Ecco un'altra risposta, con alcuni confronti IL. Considera la classe:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

Ora guarda l'IL che ogni metodo produce. Anche se i codici op non significano nulla per te, puoi vedere una grande differenza: isinst viene chiamato seguito da castclass nel metodo DirectCast. Quindi due chiamate invece di una praticamente.

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

La parola chiave isinst rispetto alla castclass

Questo post sul blog ha un discreto confronto tra i due modi di farlo. Il suo riassunto è:

  • In un confronto diretto, isinst è più veloce di castclass (anche se solo leggermente)
  • Quando è necessario eseguire controlli per assicurarsi che la conversione abbia avuto esito positivo, isinst è stato significativamente più veloce di castclass
  • Una combinazione di isinst e castclass non dovrebbe essere utilizzata in quanto molto più lenta della conversione "sicura" più rapida (oltre il 12% più lenta)

Personalmente uso sempre As, perché è facile da leggere ed è raccomandato dal team di sviluppo .NET (o comunque Jeffrey Richter)


Stavo cercando una chiara spiegazione per il casting vs as, questa risposta mi rende molto più chiara in quanto implica una spiegazione dettagliata della lingua intermedia comune. Grazie!
Morse,

18

Una delle differenze più sottili tra i due è che la parola chiave "as" non può essere utilizzata per il cast quando è coinvolto un operatore di cast:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

Questo non verrà compilato (anche se penso lo abbia fatto nelle versioni precedenti) sull'ultima riga poiché le parole chiave "as" non tengono conto degli operatori di cast. La linea string cast = (string)f;funziona benissimo però.


12

come non genera mai un'eccezione se non può invece eseguire la conversione restituendo null ( poiché funziona solo sui tipi di riferimento). Quindi usare as equivale sostanzialmente a

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

I cast di tipo C, d'altra parte, generano un'eccezione quando non è possibile alcuna conversione.


4
Equivalente, sì, ma non è lo stesso. Questo genera molto più codice di così.
plinto

10

Non proprio una risposta alla tua domanda, ma quello che penso sia un punto correlato importante.

Se stai programmando su un'interfaccia non dovresti aver bisogno di trasmettere. Speriamo che questi calchi siano molto rari. In caso contrario, probabilmente dovrai ripensare alcune delle tue interfacce.


Il casting, finora, è stato principalmente necessario per il mio Unit Testing, ma grazie per averlo presentato. Lo terrò a mente mentre ci lavoro.
Frank V,

D'accordo con il rospo, sono anche curioso di sapere perché l'aspetto del test unitario è rilevante per il casting per te @Frank V. Dove c'è bisogno di casting, spesso c'è bisogno di riprogettazione o refactoring poiché suggerisce che stai provando a calzare diversi problemi dove dovrebbero essere gestiti diversamente.
Il senatore

@TheSenator Questa domanda ha ben più di 3 anni, quindi non ricordo davvero. Ma probabilmente stavo usando le interfacce in modo aggressivo anche durante i test delle unità. Forse perché stavo usando il modello di fabbrica e non avevo accesso a un costruttore pubblico sugli oggetti di destinazione da testare.
Frank V

9

Si prega di ignorare i consigli di Jon Skeet, in riferimento a: evitare lo schema di prova e lancio, ad es .:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

L'idea che questo costa più di un cast e un test null è un MITO :

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

È una micro-ottimizzazione che non funziona. Ho eseguito alcuni test reali e test-and-cast è in realtà più veloce del confronto cast-and-null, ed è anche più sicuro perché non hai la possibilità di avere un riferimento null nell'ambito al di fuori dell'if if fallire.

Se vuoi un motivo per cui test-and-cast è più veloce, o almeno non più lento, c'è un motivo semplice e complesso.

Semplice: anche i compilatori ingenui uniranno due operazioni simili, come test-and-cast, in un singolo test e ramo. cast-and-null-test può forzare due test e un ramo, uno per il test di tipo e la conversione in null in caso di fallimento, uno per il controllo null stesso. Per lo meno, entrambi si ottimizzeranno su un singolo test e ramo, quindi test-and-cast non sarebbe né più lento né più veloce di cast-and-null-test.

Complesso: perché test-and-cast è più veloce: cast-and-null-test introduce un'altra variabile nello scope esterno che il compilatore deve tenere traccia della vivacità e potrebbe non essere in grado di ottimizzare quella variabile a seconda della complessità del controllo- il flusso è. Al contrario, test-and-cast introduce una nuova variabile solo in un ambito delimitato in modo che il compilatore sappia che la variabile è morta dopo la chiusura dell'ambito, e quindi può ottimizzare meglio l'allocazione dei registri.

Quindi, per favore, PER FAVORE lascia che questo consiglio "cast-and-null-test sia meglio del test-and-cast" DIE. PER FAVORE. test-and-cast è sia più sicuro che più veloce.


7
@naasking: se esegui il test due volte (come per il tuo primo frammento), è possibile che il tipo cambi tra i due test, se si tratta di un campo o refparametro. È sicuro per le variabili locali, ma non per i campi. Sarei interessato a eseguire i tuoi benchmark, ma il codice che hai fornito nel tuo post sul blog non è completo. Concordo con la non ottimizzazione micro, ma non penso che usare il valore due volte sia più leggibile o elegante dell'uso di "as" e di un test di nullità. (Avrei sicuramente usato un cast etero piuttosto che "come" dopo un'is, tra.)
Jon Skeet,

5
Inoltre non vedo perché sia ​​più sicuro. Ho dimostrato perché è meno sicuro, in effetti. Certo, si finisce con una variabile nell'ambito che può essere nulla, ma a meno che non si inizi a usarla al di fuori dell'ambito del successivo blocco "if", si sta bene. La preoccupazione per la sicurezza che ho sollevato (attorno ai campi che cambiano il loro valore) è una vera preoccupazione per il codice mostrato - la tua preoccupazione per la sicurezza richiede agli sviluppatori di essere lassisti in altri codici.
Jon Skeet,

1
+1 per sottolineare che è / cast o as / cast non è più lento nella realtà, intendiamoci. Avendo eseguito personalmente un test completo, posso confermare che non fa alcuna differenza per quanto posso vedere - e francamente puoi eseguire un numero sbalorditivo di cast in pochissimo tempo. Aggiornerò la mia risposta con il codice completo.
Jon Skeet,

1
In effetti, se l'associazione non è un locale, esiste la possibilità di un bug TOCTTOU (tempo di controllo a tempo di utilizzo), quindi un buon punto lì. Per quanto riguarda il motivo per cui è più sicuro, lavoro con molti sviluppatori junior che amano riutilizzare la gente del posto per qualche motivo. cast-and-null è quindi un vero pericolo per la mia esperienza e non ho mai incontrato una situazione TOCTTOU poiché non ho progettato il mio codice in quel modo. Per quanto riguarda la velocità dei test di runtime, è persino più veloce della spedizione virtuale [1]! Ri: codice, vedrò se riesco a trovare la fonte per il test del cast. [1] higherlogics.blogspot.com/2008/10/…
naasking il

1
@naasking: Non ho mai incontrato il problema del riutilizzo locale, ma direi che è più facile individuare la revisione del codice rispetto al più sottile bug TOCTTOU. Vale anche la pena sottolineare che ho appena rieseguito il mio benchmark verificando le interfacce anziché una classe sigillata, e questo dà consigli sulle prestazioni a favore di as-then-null-check ... ma come ho già detto, le prestazioni non sono Per questo motivo sceglierei un approccio particolare qui.
Jon Skeet,

4

Se il cast fallisce, la parola chiave 'as' non genera un'eccezione; imposta invece la variabile su null (o sul suo valore predefinito per i tipi di valore).


3
Nessun valore predefinito per i tipi di valore. Poiché non può essere utilizzato per i tipi di valore di lancio.
Patrik Hägne,

2
La parola chiave "as" in realtà non funziona sui tipi di valore, quindi è sempre impostata su null.
Erik van Brakel,

4

Questa non è una risposta alla domanda ma commenta l'esempio di codice della domanda:

Di solito non è necessario eseguire il cast di un oggetto, ad esempio da IMyInterface a MyClass. La cosa grandiosa delle interfacce è che se prendi un oggetto come input che implementa un'interfaccia, allora non devi preoccuparti del tipo di oggetto che stai ottenendo.

Se si esegue il cast di IMyInterface su MyClass, si suppone già di ottenere un oggetto di tipo MyClass e non ha senso utilizzare IMyInterface, perché se si alimenta il codice con altre classi che implementano IMyInterface, si romperà il codice ...

Ora, il mio consiglio: se le tue interfacce sono ben progettate puoi evitare un sacco di typecasting.


3

L' asoperatore può essere utilizzato solo su tipi di riferimento, non può essere sovraccaricato e tornerà nullse l'operazione non riesce. Non genererà mai un'eccezione.

Il casting può essere utilizzato su qualsiasi tipo compatibile, può essere sovraccaricato e genererà un'eccezione se l'operazione non riesce.

La scelta di quale utilizzare dipende dalle circostanze. In primo luogo, si tratta di stabilire se si desidera generare un'eccezione in caso di conversione non riuscita.


1
'as' può essere utilizzato anche su tipi di valore nullable, che fornisce un modello interessante. Vedi la mia risposta per il codice.
Jon Skeet,

1

La mia risposta riguarda solo la velocità nei casi in cui non controlliamo il tipo e non controlliamo i null dopo il casting. Ho aggiunto altri due test al codice di Jon Skeet:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    static void FindLengthWithIsAndCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string)o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Risultato:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

Non cercare di concentrarti sulla velocità (come ho fatto io) perché tutto ciò è molto molto veloce.


Allo stesso modo, nei miei test, ho scoperto che la asconversione (senza il controllo degli errori) ha funzionato circa l'1-3% più veloce rispetto al cast (circa 540ms contro 550ms su 100 milioni di iterazioni). Nessuno dei due farà o romperà la tua candidatura.
palswim,

1

Oltre a tutto ciò che è stato già esposto qui, ho appena trovato una differenza pratica che penso valga la pena notare, tra casting esplicito

var x = (T) ...

rispetto all'utilizzo asdell'operatore.

Ecco l'esempio:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

In conclusione : GenericCaster2 non funzionerà con i tipi di struttura. Lo farà GenericCaster.


1

Se si utilizzano le PIA di Office destinate a .NET Framework 4.X, è necessario utilizzare la parola chiave as , altrimenti non verrà compilata.

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

Il cast è corretto quando si sceglie come target .NET 2.0:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

Quando si prende di mira .NET 4.X gli errori sono:

errore CS0656: membro del compilatore mancante richiesto "Microsoft.CSharp.RuntimeBinder.Binder.Convert"

errore CS0656: membro del compilatore mancante richiesto "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create"


0

La asparola chiave funziona come un cast esplicito tra tipi di riferimento compatibili con la principale differenza che non genera un'eccezione se la conversione non riesce. Piuttosto, produce un valore nullo nella variabile target. Poiché le eccezioni sono molto costose in termini di prestazioni, è considerato un metodo molto migliore di casting.


Non è lo stesso, poiché uno chiama CastClass e l'altro chiama IsInst nel codice IL.
Jenix,

0

Ciò che scegli fortemente dipende da ciò che è richiesto. Preferisco il casting esplicito

IMyInterface = (IMyInterface)someobj;

perché se l'oggetto dovrebbe essere del tipo IMyInterface e non lo è, è sicuramente un problema. È meglio ottenere l'errore il più presto possibile perché verrà corretto l'errore esatto invece di correggere l'effetto collaterale.

Ma se hai a che fare con metodi che accettano objectcome parametro, devi verificarne il tipo esatto prima di eseguire qualsiasi codice. In tal caso assarebbe utile in modo da poterlo evitare InvalidCastException.


0

Dipende, vuoi controllare null dopo aver usato "as" o preferisci che la tua app generi un'eccezione?

La mia regola empirica è se mi aspetto sempre che la variabile sia del tipo che mi aspetto al momento in cui voglio usare un cast. Se è possibile che la variabile non esegua il cast di ciò che voglio e sono pronto a gestire i null dall'uso di as, userò as.



0

Il problema del PO è limitato a una situazione di lancio specifica. Il titolo copre molte più situazioni.
Ecco una panoramica di tutte le situazioni di casting rilevanti che attualmente posso pensare:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
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.