关于c#:’is’与使用null检查的try cast相比

'is' versus try cast with null check

我注意到Resharper建议我把这个:

1
2
3
4
if (myObj.myProp is MyType)
{
   ...
}

进入这个:

1
2
3
4
5
var myObjRef = myObj.myProp as MyType;
if (myObjRef != null)
{
   ...
}

为什么会建议这种改变?我习惯于重新分析建议优化更改和代码缩减更改,但这感觉像是它想采用我的单个语句并将其转换为两行程序。

根据msdn:

An is expression evaluates to true if both of the following conditions
are met:

expression is not null. expression can be cast to type. That is, a
cast expression of the form (type)(expression) will complete without
throwing an exception.

我是否误解了这一点,或者不是is只在一行中执行完全相同的检查,而不需要为空检查显式地创建另一个局部变量?


因为只有一个演员。比较一下:

1
2
3
4
5
6
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
    ...
}

对此:

1
2
3
4
5
6
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支持使用模式匹配的更紧凑的语法:

1
2
3
4
if (myObj.myProp is MyType myObjRef)
{
    ...
}


最好的选择是使用这样的模式匹配:

1
2
3
4
5
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


目前还没有关于皮带下面实际发生的情况的信息。看看这个例子:

1
2
3
4
5
object o ="test";
if (o is string)
{
    var x = (string) o;
}

这转化为以下IL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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倍。


重新竖琴警告:

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

两者都可以工作,这取决于您的代码如何更适合您。在我的例子中,我只是忽略了那个警告:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//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();

在我的代码中,第二种方法的性能越来越长,越来越差。


对我来说,这似乎取决于它是否会是那种类型的可能性。如果对象大部分时间都是这种类型,那么做前向投影肯定会更有效。如果只是偶尔出现这种情况,那么最好先检查一下IS。

与类型检查的成本相比,创建局部变量的成本可以忽略不计。

可读性和范围通常是我更重要的因素。我不同意Resharper,因此只使用"is"操作符;如果这是一个真正的瓶颈,请稍后进行优化。

(我假设您在这个函数中只使用一次myObj.myProp is MyType)


我想说这是为了制作一个强类型的myobj.myprop版本,它是myobjref。然后,当您在块中引用此值时,应该使用此选项,而不必执行强制转换。

例如,这:

1
myObjRef.SomeProperty

比这更好:

1
((MyType)myObj.myProp).SomeProperty


它还应该建议进行第二次更改:

1
(MyType)myObj.myProp

进入之内

1
myObjRef

与原始代码相比,这将保存属性访问和强制转换。但只有在将is改为as后才有可能。