'is' по сравнению с попыткой приведения с нулевой проверкой

Я заметил, что Resharper предлагает мне это сделать:

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

в это:

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

Почему он предложил это изменение? Я привык к Resharper, предлагая изменения в оптимизации и изменения кода, но похоже, что он хочет взять мой единственный оператор и превратить его в двухстрочный.

Согласно MSDN:

выражение оценивается как true, если оба следующих условия: выполняются:

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

Я неправильно понимаю это или не выполняет те же проверки is только в одной строке без необходимости явно создавать другую локальную переменную для нулевой проверки?

Ответ 1

Потому что там только один актерский состав. Сравните это:

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
    ...
}

С# 7.0 поддерживает более компактный синтаксис с использованием соответствия шаблонов:

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

Ответ 2

Там нет информации о том, что на самом деле происходит ниже пояса. Взгляните на этот пример:

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

Это означает следующий IL:

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 раза быстрее.

Ответ 3

Лучшим вариантом является сопоставление шаблонов использования, например:

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

Ответ 4

Мне кажется, что это зависит от того, каковы шансы на то, что он будет такого типа или нет. Разумеется, было бы более эффективно делать бросок спереди, если объект имеет этот тип большую часть времени. Если это только изредка такого типа, тогда может быть более оптимальным сначала проверить.

Стоимость создания локальной переменной очень незначительна по сравнению со стоимостью проверки типа.

Читаемость и охват - наиболее важные для меня факторы. Я бы не согласился с ReSharper и использовал оператор "is" только по этой причине; оптимизируйте позже, если это истинное узкое место.

(Я предполагаю, что вы используете только myObj.myProp is MyType один раз в этой функции)

Ответ 5

Предупреждение о повторной настройке:

"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();

В моем коде второй путь - это более продолжительная и худшая производительность.

Ответ 6

Также должно быть предложено второе изменение:

(MyType)myObj.myProp

в

myObjRef

Это сохраняет доступ к свойствам и отливку по сравнению с исходным кодом. Но это возможно только после изменения is на as.

Ответ 7

Я бы сказал, что это сделать сильно типизированную версию myObj.myProp, которая является myObjRef. Затем это следует использовать, когда вы ссылаетесь на это значение в блоке, а не на выполнение.

Например, это:

myObjRef.SomeProperty

лучше, чем это:

((MyType)myObj.myProp).SomeProperty