'є' проти спробувати з нульовою перевіркою


107

Я помітив, що Решарпер пропонує мені це перетворити:

if (myObj.myProp is MyType)
{
   ...
}

в це:

var myObjRef = myObj.myProp as MyType;
if (myObjRef != null)
{
   ...
}

Чому він пропонує цю зміну? Я звик до Resharper, що пропонує зміни оптимізації та зменшення коду, але це відчуває, що він хоче взяти моє єдине твердження і перетворити його на дволанковий.

За даними MSDN :

Це вираз буде істинним , якщо обидва з наступних умов:

вираз не є нульовим. вираз може бути передано на тип . Тобто виразний вираз форми (type)(expression)буде завершений, не кидаючи винятку.

Чи я неправильно читаю, що чи не isроблю точно такі ж перевірки, просто в одному рядку без необхідності явно створювати ще одну локальну змінну для нульової перевірки?


1
ви використовуєте myObjRef пізніше в коді? якщо ви є, вам не потрібен був би MyPropгеть після цієї зміни.
За замовчуванням

Відповіді:


146

Тому що є лише один акторський склад. Порівняйте це:

if (myObj.myProp is MyType) // cast #1
{
    var myObjRef = (MyType)myObj.myProp; // needs to be cast a second time
                                         // before using it as a MyType
    ...
}

до цього:

var myObjRef = myObj.myProp as MyType; // only one cast
if (myObjRef != null)
{
    // myObjRef is already MyType and doesn't need to be cast again
    ...
}

C # 7.0 підтримує більш компактний синтаксис, використовуючи відповідність шаблонів :

if (myObj.myProp is MyType myObjRef)
{
    ...
}

3
точно. використання 'є' в основному робить щось на кшталт повернення ((myProp як MyType) == null)
Bambu

2
Що стосується змін, це досить хвилини. Нульова перевірка буде досить порівнянна з чеком другого типу. asможе бути на пару наносекунд швидше, але я вважаю це передчасною мікрооптимізацією.
Сервіс

4
Також зауважте, що оригінальна версія не є безпечною для потоків. Значення myObjабо myPropможе бути змінене (іншим потоком) між isі передачею, викликаючи небажану поведінку.
Джефф Е

1
Я також можу додати, що за допомогою as+ != nullтакож буде виконуватися переопределений !=оператор, MyTypeякщо визначено (навіть якщо він myObjRefє нульовим). Хоча в більшості випадків це не проблема (особливо якщо ви правильно її реалізуєте), у деяких крайніх випадках (поганий код, продуктивність) це може бути не бажаним. (мав би бути досить екстремальним )
Кріс Сінклер

1
@Chris: Правильно, використовувався правильний переклад коду object.ReferenceEquals(null, myObjRef).
Бен Войгт

10

Найкращим варіантом є використання відповідного шаблону:

if (value is MyType casted){
    //Code with casted as MyType
    //value is still the same
}
//Note: casted can be used outside (after) the 'if' scope, too

Наскільки саме цей кращий за другий фрагмент із запитання?
Віктор Ярема

Другий фрагмент запитання стосується основного використання (без змінної декларації), і в цьому випадку ви двічі перевірите тип (один в операторі is, а інший перед виступом)
Франческо Каттоні

6

Ще немає інформації про те, що насправді відбувається під поясом. Погляньте на цей приклад:

object o = "test";
if (o is string)
{
    var x = (string) o;
}

Це означає наступний ІЛ:

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  ldnull      
IL_000E:  cgt.un      
IL_0010:  stloc.1     
IL_0011:  ldloc.1     
IL_0012:  brfalse.s   IL_001D
IL_0014:  nop         
IL_0015:  ldloc.0     // o
IL_0016:  castclass   System.String
IL_001B:  stloc.2     // x
IL_001C:  nop         
IL_001D:  ret   

Тут важливі isinstі castclassдзвінки - і відносно дорогі. Якщо ви порівнюєте це з альтернативою, ви можете бачити, що це лише isinstперевірка:

object o = "test";
var oAsString = o as string;
if (oAsString != null)
{

}

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  stloc.1     // oAsString
IL_000E:  ldloc.1     // oAsString
IL_000F:  ldnull      
IL_0010:  cgt.un      
IL_0012:  stloc.2     
IL_0013:  ldloc.2     
IL_0014:  brfalse.s   IL_0018
IL_0016:  nop         
IL_0017:  nop         
IL_0018:  ret  

Також варто зазначити, що тип значення буде використовуватись, unbox.anyа не castclass:

object o = 5;
if (o is int)
{
    var x = (int)o;
}

IL_0000:  nop         
IL_0001:  ldc.i4.5    
IL_0002:  box         System.Int32
IL_0007:  stloc.0     // o
IL_0008:  ldloc.0     // o
IL_0009:  isinst      System.Int32
IL_000E:  ldnull      
IL_000F:  cgt.un      
IL_0011:  stloc.1     
IL_0012:  ldloc.1     
IL_0013:  brfalse.s   IL_001E
IL_0015:  nop         
IL_0016:  ldloc.0     // o
IL_0017:  unbox.any   System.Int32
IL_001C:  stloc.2     // x
IL_001D:  nop         
IL_001E:  ret   

Однак зауважте, що це не обов'язково означає швидший результат, як ми бачимо тут . Там , здається, були поліпшення , так як це питання було поставлено питання , хоча: зліпки , здається , повинні бути виконані так само швидко , як вони раніше, але asі linqв даний час приблизно в 3 рази швидше.


4

Попередження про повторне формування:

"Type check and direct cast can be replaced with try cast and check for null"

Обидва будуть працювати, це залежить від того, як ваш код вам більше підходить. У моєму випадку я просто ігнорую це попередження:

//1st way is n+1 times of casting
if (x is A) ((A)x).Run();
else if (x is B) ((B)x).Run();
else if (x is C) ((C)x).Run();
else if (x is D) ((D)x).Run();
//...
else if (x is N) ((N)x).Run();    
//...
else if (x is Z) ((Z)x).Run();

//2nd way is z times of casting
var a = x as Type A;
var b = x as Type B;
var c = x as Type C;
//..
var n = x as Type N;
//..
var z = x as Type Z;
if (a != null) a.Run();
elseif (b != null) b.Run();
elseif (c != null) c.Run();
...
elseif (n != null) n.Run();
...
elseif (x != null) x.Run();

У моєму коді другий спосіб - це довша та гірша продуктивність.


1
У вашому прикладі реального світу існує просто проблема дизайну. Якщо ви керуєте типами, просто використовуйте такий інтерфейс, як IRunable. Якщо у вас немає контролю, можливо, ви могли б скористатися dynamic?
М. Мімпен

3

Мені це здається залежним від того, які шанси на те, чи буде він такого типу чи ні. Безумовно, було б ефективніше зробити литий фронт, якщо об’єкт такого типу проводиться більшу частину часу. Якщо це лише час від часу такого типу, то, можливо, найоптимальніше спочатку перевірити це.

Вартість створення локальної змінної дуже незначна порівняно з вартістю перевірки типу.

Чтенність та обсяг - це для мене найважливіші фактори. Я б не погоджувався з ReSharper і використовував оператор "є" лише з цієї причини; оптимізуйте пізніше, якщо це справжнє вузьке місце.

(Я припускаю, що ви використовуєте myObj.myProp is MyTypeцю функцію лише один раз)


0

Також слід запропонувати другу зміну:

(MyType)myObj.myProp

в

myObjRef

Це економить доступ до власності та вигляд у порівнянні з вихідним кодом. Але це можливо лише після зміни isна as.


@ За замовчуванням: Ні, це не так. Це не означає, що його немає в коді.
Бен Войгт

1
вибачте .. неправильно зрозумів. однак, (MyType)викине виняток, якщо кастинг не вдасться. asтільки повертається null.
Типово

@ За замовчуванням: Передача не завершиться невдачею, оскільки тип уже перевірено is(цей код є у питанні).
Бен Войгт

1
однак re # хоче замінити цей код - це означає, що він не буде там після запропонованої зміни.
Типово

Я думаю, що я слідую за вашою думкою тут (просто зайняв мене трохи часу). Ви маєте на увазі, що перший рядок знаходиться десь у коді, і цей рядок буде спрощено після пропозиції Re # до другого рядка?
Типово

0

Я б сказав, що це зробити сильно набрану версію myObj.myProp, яка є myObjRef. Потім це слід використовувати, коли ви посилаєтесь на це значення в блоці, порівняно з тим, щоб зробити акторський склад.

Наприклад, це:

myObjRef.SomeProperty

краще за це:

((MyType)myObj.myProp).SomeProperty
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.