La risposta sotto la riga è stata scritta nel 2008.
C # 7 ha introdotto la corrispondenza dei modelli, che ha ampiamente sostituito l' as
operatore, 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 if
corpo.) 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 is
seguito 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 randomObject
tratta 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 randomObject
tra i due.
Se randomObject
davvero 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 TargetType
ed 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 TargetType
ed TargetType
è un tipo di valore, allora non possiamo usare as
con TargetType
se 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' is
operatore è 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 is
per il caso del tipo di valore prima d'ora, non avendo pensato di usare un tipo nullable e as
insieme :)
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.