'is'与尝试使用空检查进行转换


107

我注意到Resharper建议我转一下这个:

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

到这个:

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

为什么会建议这种变化?我习惯于Resharper提出优化更改和代码减少更改的建议,但这感觉就像是要接受我的单个语句并将其变成两条线。

根据MSDN

一个 表达式的计算结果为真,如果同时满足以下两个条件都满足:

表达式不为null。可以将表达式转换为type。也就是说,表单的强制转换表达式(type)(expression)将完成而不会引发异常。

is是否只是在一行中误读了该代码,或者不执行完全相同的检查,而无需为空检查显式创建另一个局部变量?


1
您稍后在代码中使用myObjRef吗?如果是这样,则MyProp在进行此更改后就不需要使用吸气剂。
默认值

Answers:


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
就变化而言,这是很短的时间。空检查将与第二种类型检查相当。 as可能要快几纳秒,但我认为这是过早的微优化。
Servy

4
另请注意,原始版本不是线程安全的。myObj或的值myProp可能会在is和强制转换之间更改(通过另一个线程),从而导致不良行为。
杰夫E

1
我还可以补充一点,使用as+ != null还将执行if定义的重写!=运算符MyType(即使myObjRef为null)。尽管在大多数情况下这不是问题(特别是如果您正确实现了它),但在某些极端情况下(错误的代码,性能),则可能不希望这样做。(虽然必须非常极端
克里斯·辛克莱

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

这个问题比问题中的第二个问题要好多少?
维克多·亚雷玛

问题的第二个片段指的是is的基本用法(不带变量声明),在这种情况下,您将检查类型两次(一个在is语句中,另一个在转换之前)
Francesco Cattoni

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

转载警告:

"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 '16

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

我要说的是要制作myObj.myProp的强类型版本,即myObjRef。然后,当您在块中引用此值时,就必须使用它,而不必进行强制转换。

例如,这:

myObjRef.SomeProperty

比这更好:

((MyType)myObj.myProp).SomeProperty
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.