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

107

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

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

в это:

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

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

Согласно MSDN :

Это выражение будет истинным , если оба из следующих условий:

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

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

HotN
источник
1
вы используете myObjRef позже в коде? если да, то MyPropпосле этого изменения вам не понадобится геттер.
Default

Ответы:

147

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

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
именно. использование 'is' в основном делает что-то вроде return ((myProp as MyType) == null)
Bambu
2
Что касается изменений, то это довольно минутка. Нулевая проверка будет очень похожа на проверку второго типа. asможет быть на пару наносекунд быстрее, но я считаю это преждевременной микрооптимизацией.
Servy
4
Также обратите внимание, что исходная версия не является потокобезопасной. Значение myObjили myPropмогло быть изменено (другим потоком) между приведением isи приведением, вызывая нежелательное поведение.
Jeff E
1
Я мог бы также добавить, что использование as+ != nullтакже будет выполнять переопределенный !=оператор MyTypeif defined (даже если он myObjRefравен нулю). Хотя в большинстве случаев это не проблема (особенно если вы правильно ее реализуете), в некоторых крайних случаях (плохой код, производительность) это может быть нежелательно. ( хотя это должно было бы быть довольно экстремальным )
Крис Синклер
1
@Chris: Верно, правильный перевод кода будет использовать object.ReferenceEquals(null, myObjRef).
Ben Voigt
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 (без объявления переменной), и в этом случае вы дважды проверите тип (один в операторе is, а другой перед приведением)
Франческо Каттони,
6

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

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

Йерун Ванневель
источник
4

Предупреждение Resharper:

"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 и использовал оператор is только по этой причине; оптимизируйте позже, если это действительно узкое место.

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

Деррик
источник
0

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

(MyType)myObj.myProp

в

myObjRef

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

Бен Фойгт
источник
@ По умолчанию: Нет, это не так. Это не значит, что этого нет в коде.
Ben Voigt
1
извините .. неправильно понял. однако (MyType)вызовет исключение, если приведение не удастся. asтолько возвращается null.
Default
@ По умолчанию: приведение не завершится ошибкой, потому что тип уже был проверен is(этот код находится в вопросе).
Ben Voigt
1
однако re # хочет заменить этот код - то есть его не будет после предложенного изменения.
Default
Я думаю , что я следую за вашу мысль здесь (только у меня ушло какое - то время). Вы имеете в виду, что первая строка находится где-то в коде, и эта строка будет упрощена после предложения Re # до второй строки?
Default
0

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

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

myObjRef.SomeProperty

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

((MyType)myObj.myProp).SomeProperty
Джерад Роуз
источник