Відповідь під рядком була написана у 2008 році.
C # 7 запровадив відповідність шаблонів, яка значною мірою замінила asоператора, як ви тепер можете написати:
if (randomObject is TargetType tt)
{
// Use tt here
}
Зауважимо, що ttце все ще є в обсягах після цього, але не визначено визначено. (Це , безумовно, призначається всередині ifтіла.) У деяких випадках це трохи дратує, тож якщо ви дійсно піклуєтеся про введення найменшої кількості змінних, можливих у будь-якій області, ви, можливо, все-таки захочете скористатись isподальшим перекладом.
Я не думаю, що жодна з відповідей поки що (на момент початку цієї відповіді!) Насправді пояснила, де варто використовувати яку.
Не робіть цього:
// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
TargetType foo = (TargetType) randomObject;
// Do something with foo
}
Ця перевірка не тільки двічі, але може перевіряти різні речі, якщо randomObjectце поле, а не локальна змінна. Можливо, якщо "if" пройде, але тоді випадок не вдасться, якщо інший потік змінює значення randomObjectміж ними.
Якщо randomObjectдійсно має бути екземпляр TargetType, тобто якщо це не так, це означає, що виникла помилка, тоді кастинг - це правильне рішення. Це викидає виняток негайно, це означає, що більше не виконується робота за неправильними припущеннями, і виняток правильно показує тип помилки.
// 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;
Якщо це randomObject може бути екземпляр TargetTypeі TargetTypeпосилальний тип, тоді використовуйте такий код:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
Якщо це randomObject може бути екземпляр TargetTypeі TargetTypeє типом значення, то ми не можемо використовувати asз TargetTypeсобою, але ми можемо використовувати тип нульового значення:
TargetType? convertedRandomObject = randomObject as TargetType?;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject.Value
}
(Зауважте: наразі це насправді повільніше, ніж це + ролі . Я думаю, що це більш елегантно і стійко, але ми йдемо.)
Якщо ви дійсно не потрібно перетворене значення, але ви просто повинні знати , є чи він є екземпляром TargetType, то isоператор є вашим другом. У цьому випадку не має значення, чи є TargetType еталонним типом чи типом значення.
Можуть бути й інші випадки, пов’язані з дженериками, де isце корисно (тому що ви не можете знати, чи T є еталонним типом чи ні, тому ви не можете використовувати як), але вони відносно неясні.
Я майже напевно використовував isдля цього випадку значення значення, не думаючи використовувати нульовий тип asразом разом :)
EDIT: Зауважте, що жоден з перерахованих вище не говорить про продуктивність, окрім випадків типу значення, де я зазначив, що розпакування до типу зведеного значення насправді повільніше - але послідовно.
Відповідно до відповіді naasking, "і-кинутий", або "є" і "настільки ж швидкі, як" і "-" ", як це показано в коді нижче:
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);
}
}
На моєму ноутбуці вони виконуються приблизно за 60 мс. Дві речі, які слід зазначити:
- Значної різниці між ними немає. (Насправді, бувають ситуації, коли перевірка типу «плюс-нуль», безумовно , повільніше. Наведений вище код фактично робить перевірку типу простою, оскільки це для герметичного класу; якщо ви перевіряєте інтерфейс, баланс трохи підказує на користь перевірки як плюс-нуль.)
- Всі вони шалено швидко. Це просто не буде вузьким місцем у вашому коді, якщо ви дійсно не збираєтесь робити нічого зі значеннями згодом.
Тож давайте не будемо хвилюватися щодо виступу. Давайте турбуватися про правильність і послідовність.
Я стверджую, що і-і-передані (або є-і-як) є і небезпечними при роботі зі змінними, оскільки тип значення, на яке він посилається, може змінюватися через інший потік між тестом і групою. Це було б досить рідкісною ситуацією - але я скоріше маю конвенцію, яку я можу послідовно використовувати.
Я також стверджую, що перевірка як недійсна дає змогу краще розділити проблеми. У нас є одне твердження, яке намагається перетворити, а потім одне, яке використовує результат. Ідентифікатор is-and-cast або is-and-a виконує тест, а потім ще одну спробу перетворення значення.
Іншими словами, буде хто - небудь коли - небудь написати:
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
// Use value
}
Це щось із того, що робиться і кидається, хоча, очевидно, досить дешевим способом.