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

The "is" type pattern expression for null check

我可以重构这段代码(流行的 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) { ... }

..进入:

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

注意:没有 asSomeMethod() 已经 returns MyType。它看起来像(伪代码)if(A is A) 并且很容易混淆,不是吗?

第一个重构是合法的,那么后一个呢?我不是 IL 专家来检查自己,C# 7.0 功能对我来说仍然是新的。或许还有我没有发现的问题?

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

编译器基本上按如下方式处理它们(对于引用类型)

第一

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. 可能更多的是可打印字符,所以如果你有可打印字符强迫症或者遭受残酷的代码审查,这可能不是一件好事
  4. 如果你喜欢它并且你的团队也喜欢它,那就去吧。
  5. 这不是我的菜

我发现编译器非常智能。 is 表达式有多种翻译变体:

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

  1. SomeMethod returns 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 returns 其他类型 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 或类似工具检查所有这些变体。

我不会用它来对引用类型执行身份转换,因为 null 检查对未来来说更直观 reader。

对于可空类型,情况完全不同。给定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非常有用并且显着有助于提高可读性。

在c#8中,使用模式匹配表达式测试非空的sugar表达式:

if (name is {})  // name !=null
   Console.WriteLine("name is not null")