'is'대 null 검사로 시도 캐스트


107

Resharper가 이것을 돌리라고 제안하는 것을 발견했습니다.

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

이것으로 :

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

왜 이러한 변화를 제안합니까? 저는 Resharper가 최적화 변경과 코드 감소 변경을 제안하는 데 익숙하지만, 이것은 내 단일 문장을 두 줄로 바꾸고 싶은 것처럼 느껴집니다.

MSDN 에 따르면 :

이다 발현 다음 두 조건이 모두 충족 될 경우는 true로 평가

식이 null이 아닙니다. expression은 유형 으로 캐스트 될 수 있습니다 . 즉, 양식의 캐스트 표현식은 (type)(expression)예외를 발생시키지 않고 완료됩니다.

isnull 검사를 위해 다른 지역 변수를 명시 적으로 만들 필요없이 한 줄에 정확히 동일한 검사를 수행 하지 않거나 잘못 읽었 습니까?


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
바로 그거죠. 'is'를 사용하는 것은 기본적으로 return ((myProp as MyType) == null)
Bambu

2
변경 사항에 관한 한, 이것은 꽤 분입니다. null 검사는 두 번째 유형 검사와 매우 비슷할 것입니다. as몇 나노초가 더 빠를 수 있지만 이것은 조기 마이크로 최적화라고 생각합니다.
Servy

4
또한 원래 버전은 스레드로부터 안전하지 않습니다. myObj또는 의 값이 와 캐스트 myProp간에 변경 (다른 스레드에 의해)되어 is바람직하지 않은 동작을 유발할 수 있습니다.
Jeff E

1
as+ 를 사용 하면 if 정의 된 연산자 ( null 인 경우 != null에도)를 실행할 수도 있습니다 . 대부분의 경우 이것은 문제가되지 않지만 (특히 올바르게 구현 한 경우) 일부 극단적 인 경우 (나쁜 코드, 성능)에서는 바람직하지 않을 수 있습니다. (이되어야 할것 꽤 극단적 인의 생각을)!=MyTypemyObjRef
크리스 싱클레어

1
@Chris : 맞습니다. 올바른 코드 번역은 object.ReferenceEquals(null, myObjRef).
Ben Voigt 2012

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

이것이 질문의 두 번째 조각보다 정확히 얼마나 낫습니까?
Victor Yarema

질문의 두 번째 부분은 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   

여기서 중요한 것은 isinstcastclass호출입니다. 둘 다 상대적으로 비쌉니다. 그것을 대안과 비교하면 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   

그러나 이것이 반드시 여기에서 볼 수있는 것처럼 더 빠른 결과로 변환되는 것은 아닙니다 . 캐스트가 될하는 데 사용하지만, 최대한 빨리 수행 할 것 같습니다 :하지만 그 질문은 질문 이후 개선되었을 것 같다 aslinq현재 약 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?
M. Mimpen

3

나에게 이것은 그 유형이 될 가능성이 무엇인지에 달려 있습니다. 개체가 대부분의 경우 해당 유형의 경우 전면 캐스트를 수행하는 것이 확실히 더 효율적입니다. 그 유형이 가끔씩 만 발생한다면 우선 is로 확인하는 것이 더 최적 일 수 있습니다.

지역 변수를 만드는 비용은 유형 검사 비용에 비해 매우 미미합니다.

가독성과 범위는 일반적으로 나에게 더 중요한 요소입니다. 저는 ReSharper에 동의하지 않으며 그 이유만으로 "is"연산자를 사용합니다. 이것이 진정한 병목이라면 나중에 최적화하십시오.

( myObj.myProp is MyType이 기능에서 한 번만 사용한다고 가정합니다 )


0

두 번째 변경 사항도 제안해야합니다.

(MyType)myObj.myProp

으로

myObjRef

이렇게하면 원래 코드와 비교하여 속성 액세스 및 캐스트가 절약됩니다. 그러나로 변경 한 후에 만 ​​가능 is합니다 as.


@ 기본값 : 아니요. 그렇다고 코드에 없다는 의미는 아닙니다.
Ben Voigt 2012

1
미안해 .. 오해. 그러나 (MyType)캐스트가 실패하면 예외가 발생합니다. as만 반환합니다 null.
기본적으로

@Default : 유형이 이미 확인되었으므로 캐스트가 실패하지 않습니다 is(해당 코드가 문제에 있음).
Ben Voigt 2012

1
그러나 re #은 해당 코드를 대체하려고합니다. 즉, 제안 된 변경 후에는 존재하지 않을 것입니다.
기본적으로

나는 생각 (단지 좀 시간이 걸렸) 내가 여기 당신의 생각을 다음과 같은거야. 첫 번째 줄 코드의 어딘가에 있고 두 번째 줄에 대한 Re # 제안 후에 해당 줄이 단순화 된다는 것을 의미 합니까?
기본적으로

0

이것은 myObjRef 인 myObj.myProp의 강력한 형식 버전을 만드는 것입니다. 그런 다음 블록에서이 값을 참조 할 때와 캐스트를 수행해야 할 때 사용해야합니다.

예를 들면 다음과 같습니다.

myObjRef.SomeProperty

이것보다 낫다 :

((MyType)myObj.myProp).SomeProperty
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.