【问题标题】:The "is" type pattern expression for null check用于空检查的“is”类型模式表达式
【发布时间】:2019-04-11 13:03:57
【问题描述】:

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

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

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

【问题讨论】:

  • 第二个是合法的,我看过别人推荐的,但我不确定我是它的忠实粉丝......
  • @JeroenMostert:我刚刚尝试了您的if (SomeMethod() is var o 变体,但是当返回值为null 时,它实际上返回true?你确定,它在语义上是一样的吗?这实际上使我想到了一个后续问题:为什么结果不同? if (SomeMethod() is var o) 总是返回 true,而 if (SomeMethod() is object o) 在返回值为 null 时返回 false。
  • @ChristophHerold 这就是 SomeMethod() is var oSomeMethod() is MyType a 之间的区别 -- var 匹配 null,但 MyType 不匹配。
  • @Christophe var 匹配从SomeMethod() 的返回值推断的类型。 docs.microsoft.com/en-us/dotnet/csharp/… 是我能找到的最好的文档,尽管那是关于 switch 语句的。
  • IMO 这个问题相当具体:“这合法吗,有什么问题吗?”。虽然 cmets 对这是否是一个好主意有一些意见,但这不是问题的意图。

标签: c# c#-7.0


【解决方案1】:

显然这 2 个实现非常相似,memoryallocationscycles 的差异可以忽略不计。

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

第一

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) 并感谢 (5)。如果有人早些时候在 codereview/interview 中向我展示这样的代码,我自己不会猜到答案。所以我可能也会坚持(5)。
【解决方案2】:

我发现编译器非常智能。 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 或类似的工具检查所有这些变体。

【讨论】:

  • 我认为您可以放心地假定使用了a,因为语法SomeMethod() is MyType 是合法的并且会执行空检查——并且您在某些扩展中使用了它——所以声明和不使用变量只是多余的无意义的代码。
  • 有时你可能会删除一些代码并留下警告(CS0168),所以这表明is typeis type a在不使用a时是相同的,并且没有警告,这种行为与未使用的局部变量。
  • 非常有用。感谢您记录这一点!这在手动编写表达式树时尤其重要,我们无法访问is
【解决方案3】:

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

对于可为空的类型,情况就完全不同了。给定struct S,那么

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

等价于

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

避免GetValueOrDefault() 调用(或更糟糕的是,读取执行另一个 null 检查的Value 属性)在IMO 非常有用,并且显着提高了可读性。

【讨论】:

  • 感谢意见(不喜欢的人越多越好)。我没有考虑过值类型和可空值。但是GetValueOrDefault() 在第二个例子中有意义吗?是编译器生成的代码吗?在HasValue 检查之后,我会直接使用Value。还是因为线程安全?
【解决方案4】:

在 c# 8 中,使用模式匹配表达式测试 not null 的糖表达式:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 2021-02-07
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多