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 as
operatora, ponieważ można teraz pisać:
if (randomObject is TargetType tt)
{
// Use tt here
}
Zauważ, że tt
po 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ć is
rzutowania.
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 randomObject
jest 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 randomObject
naprawdę 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 TargetType
i TargetType
jest 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 TargetType
i TargetType
jest typem wartości, nie możemy użyć go as
razem 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 is
przypadku typu wartości, nie myśląc o użyciu typu zerowalnego i as
razem :)
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.