Odpowiedź poniżej linii została napisana w 2008 roku.
W C # 7 wprowadzono dopasowanie wzorca, które w dużej mierze zastąpiło asoperatora, ponieważ można teraz pisać:
if (randomObject is TargetType tt)
{
// Use tt here
}
Zauważ, że ttpo tym jest nadal w zasięgu, ale nie jest zdecydowanie przypisany. (Jest to zdecydowanie przypisane w ramachif ciele.) W niektórych przypadkach jest to nieco irytujące, więc jeśli naprawdę zależy ci na wprowadzeniu możliwie najmniejszej liczby zmiennych w każdym zakresie, możesz nadal chcieć użyć isrzutowania.
Nie sądzę, aby jakakolwiek z dotychczasowych odpowiedzi (w momencie rozpoczynania tej odpowiedzi!) Naprawdę wyjaśniała, gdzie warto jej użyć.
Nie rób tego:
// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
TargetType foo = (TargetType) randomObject;
// Do something with foo
}
Nie tylko sprawdza się to dwukrotnie, ale może sprawdzać różne rzeczy, jeśli randomObjectjest to pole, a nie zmienna lokalna. Możliwe jest, że „if” przejdzie, ale rzutowanie zakończy się niepowodzeniem, jeśli inny wątek zmieni wartośćrandomObject między nimi.
Jeśli randomObjectnaprawdę powinno to być wystąpienie TargetType, tzn. Jeśli tak nie jest, oznacza to błąd, to casting jest właściwym rozwiązaniem. To natychmiast zgłasza wyjątek, co oznacza, że nie wykonuje się żadnej pracy przy niepoprawnych założeniach, a wyjątek poprawnie pokazuje rodzaj błędu.
// 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;
Jeśli randomObject może to być instancja TargetTypei TargetTypejest typem odwołania, użyj kodu w następujący sposób:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
Jeśli randomObject może być instancją typu TargetTypei TargetTypejest typem wartości, nie możemy użyć go asrazem TargetType, ale możemy użyć typu zerowego:
TargetType? convertedRandomObject = randomObject as TargetType?;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject.Value
}
(Uwaga: obecnie jest to w rzeczywistości wolniejsze niż + cast . Myślę, że jest bardziej eleganckie i spójne, ale proszę bardzo.)
Jeśli naprawdę nie potrzebujesz przekonwertowanej wartości, ale musisz tylko wiedzieć, czy jest to instancja TargetType, a następnieis operator jest Twoim przyjacielem. W tym przypadku nie ma znaczenia, czy TargetType jest typem referencyjnym czy typem wartości.
Mogą wystąpić inne przypadki dotyczące leków generycznych is są użyteczne (ponieważ możesz nie wiedzieć, czy T jest typem referencyjnym, czy nie, więc nie możesz użyć jako), ale są one stosunkowo niejasne.
Prawie na pewno wcześniej użyłem isprzypadku typu wartości, nie myśląc o użyciu typu zerowalnego i asrazem :)
EDYCJA: Zauważ, że żadne z powyższych nie mówi o wydajności, poza przypadkiem typu wartości, w którym zauważyłem, że rozpakowanie do typu wartości zerowej jest w rzeczywistości wolniejsze - ale spójne.
Zgodnie z odpowiedzią naasking, zarówno rzutowanie jak i rzucanie jest zarówno szybkie jak i zerowe przy pomocy nowoczesnych JIT, jak pokazano w poniższym kodzie:
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);
}
}
Na moim laptopie wszystkie działają w około 60ms. Dwie rzeczy do zapamiętania:
- Nie ma między nimi znaczącej różnicy. (W rzeczywistości zdarzają się sytuacje, w których sprawdzenie „plus-zero” jest zdecydowanie wolniejsze. Powyższy kod w rzeczywistości ułatwia sprawdzanie typu, ponieważ dotyczy klasy zapieczętowanej; jeśli sprawdzasz interfejs, saldo lekko się przechyla na korzyść kontroli plus-zerowej).
- Wszystkie są niesamowicie szybkie. To po prostu nie będzie wąskim gardłem w kodzie, chyba że naprawdę nie zamierzasz nic robić z wartościami później.
Nie martwmy się więc o wydajność. Martwmy się o poprawność i spójność.
Uważam, że zarówno rzutowanie, jak i rzutowanie jest niebezpieczne w przypadku zmiennych, ponieważ rodzaj wartości, do której się odnosi, może się zmienić z powodu innego wątku między testem a rzutowaniem. To byłaby dość rzadka sytuacja - ale wolałbym mieć konwencję, z której mogę konsekwentnie korzystać.
Twierdzę również, że kontrola „jak wtedy-zero” zapewnia lepszy rozdział obaw. Mamy jedno zdanie, które próbuje przekształcić, a następnie jedno zdanie, które wykorzystuje wynik. Is-and-cast lub is-and-as wykonuje test, a następnie kolejną próbę konwersji wartości.
Innymi słowy, czy ktoś kiedykolwiek napisałby:
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
// Use value
}
Tak właśnie się dzieje - choć oczywiście w tańszy sposób.