用于空检查的“ is”类型模式表达式

时间:2019-04-11 13:03:58

标签: c# c#-7.0

我可以重构此代码(流行的as/null check模式)

var a = b as MyType;
if(a != null) { ... }

..变成一个不错的"is" type pattern expression

if(b is MyType a) { ... }

..这很酷...我想...是吗?


但是现在我也在考虑重构

var a = SomeMethod();
if(a != null) { ... }

.. into:

if(SomMethod() is MyType a) { ... }

注意:没有as,并且 SomeMethod()已经返回MyType 。看起来像(伪代码)if(A is A),很容易造成混淆,不是吗?

第一个重构是合法的,但是第二个呢?我不是要检查自己的IL专家,并且C#7.0功能对我来说仍然是新的。也许有我尚未发现的问题?

3 个答案:

答案 0 :(得分:3)

很显然,这两个实现非常相似,在内存分配周期中,差异可以忽略不计。

编译器基本上对它们进行以下处理(对于引用类型)

第一

MyType myType = SomeMethod();
if (myType != null)
{
   Console.WriteLine(myType.ToString());
}

第二

MyType myType2;
if ((object)(myType2 = SomeMethod()) != null)
{
   Console.WriteLine(myType2.ToString());
}

使用 IL

可能更好

第一

IL_0000: ldarg.0
IL_0001: call instance class C/MyType C::SomeMethod()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: brfalse.s IL_0015

IL_000a: ldloc.0
IL_000b: callvirt instance string[mscorlib] System.Object::ToString()
IL_0010: call void[mscorlib] System.Console::WriteLine(string)

第二

IL_0015: ldarg.0
IL_0016: call instance class C/MyType C::SomeMethod()
IL_001b: dup
IL_001c: stloc.1
IL_001d: brfalse.s IL_002a

IL_001f: ldloc.1
IL_0020: callvirt instance string[mscorlib] System.Object::ToString()
IL_0025: call void[mscorlib] System.Console::WriteLine(string)

注意 :您可以查看反汇编,IL和jit-asm here

IL 的区别基本上是2个操作码:

  • dup:将当前最高值复制到评估堆栈中,然后将副本推入评估堆栈中。
  • Ldloc:将特定索引处的局部变量加载到评估堆栈中。

Jitted 时,很可能将优化转换为相同的指令


摘要

  1. 没有明显的技术差异。
  2. 是的,is版本更整洁,我想它更简洁。
  3. 它可能更适合租船承租人,因此,如果您拥有可打印字符 OCD 或遭受残酷的代码审查,那可能不是一件好事
  4. 如果您喜欢它,而您的团队也喜欢它,那就去吧。
  5. 这不是我真正的茶

答案 1 :(得分:1)

我发现编译器非常智能。 is表达式有几种翻译形式:

if(SomeMethod() is MyType a) {...}

  1. SomeMethod返回MyType

    • MyType没有覆盖运算符==,并且未使用变量a

      if (SomeMethod() != null) {...}
      
    • MyType具有覆盖运算符==,但未使用变量a

      if ((object)(SomeMethod()) != null) {...}
      
    • MyType没有覆盖运算符==,并且使用了变量a

      MyType a;
      if ((a = SomeMethod()) != null) {...}
      
    • MyType具有覆盖运算符==,并且使用了变量a

      MyType a;
      if ((object)(a = SomeMethod()) != null) {...}
      
  2. SomeMethod返回其他类型,例如object

    • 未使用变量a

      if (SomeMethod() is MyType) {...}
      
    • MyType没有覆盖运算符==,并且使用了变量a

      MyType a;
      if ((a = (SomeMethod() as MyType)) != null) {...}
      
    • MyType具有覆盖运算符==,并且使用了变量a

      MyType a;
      if ((object)(a = (SomeMethod() as MyType)) != null) {...}
      

顺便说一句,您可以通过ILSpy或类似方式检查所有这些变体。

答案 2 :(得分:0)

我不会使用它对引用类型执行身份转换,因为对于将来的读者来说,空值检查会更加直观。

对于可空类型,情况则完全不同。给定struct S,然后

void foo(S? p)
{
    if (p is S s) {
        bar(s);
    }
}

等同于

void foo(S? p)
{
    if (p.HasValue) {
        bar(p.GetValueOrDefault());
    }
}

并避免进行GetValueOrDefault()调用(或更糟糕的是,读取Value属性,该属性执行另一个空检查)对IMO很有用,并大大提高了可读性。