Відповідь під рядком була написана у 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
}
Це щось із того, що робиться і кидається, хоча, очевидно, досить дешевим способом.