当不是所有代码路径 return 时,C# 编译器不会报错

C# compiler doesn't complain when not all code paths return

我有一个 returning 在 using 块中的函数:

int f() {
  using (...) {
     ...
     return someVar;
  }
}

我刚刚注意到这一点,并将 return 移到了 using 块之外,移到了最外层的函数范围,因为我觉得那是 return 应该在的地方。

但我很困惑为什么编译器没有抱怨并非所有代码路径都是 returning。是否仅仅是因为如果它无法初始化资源,我们就会崩溃,所以这无关紧要?

举个例子:

class MainClass {
  public static void Main (string[] args) {
    f();
  }

  public static int f() {
    using(A a = new A()) {
      return 1;
    }
  }
}

class A : IDisposable{
  public void Dispose() { }
}

编译器不关心我们在using中只return。但是,我认为 using 语句基本上是 try/catch

的语法糖

如果我们替换

using(A a = new A()) {
  return 1;
}

A a = new A();;
try  {
  return 1;
}

catch (Exception e) { }
finally {
  if (a != null) {
    ((IDisposable) a).Dispose();
  }
}

确实编译器抱怨:

error CS0161: `MainClass.f()': not all code paths return a value

为什么在另一种情况下它不抱怨?

仅仅是我上面说的吗? 如果它无法初始化资源,我们就会崩溃,所以编译器认为这无关紧要。

基本上,是的。 using 语句会将其异常抛出堆栈。捕获异常并使用不 return 的 finally 块意味着该方法既不会抛出异常也不会 return.

编译器也不会抱怨像下面这样的方法

public int SuperCool(){
   throw new NotImplementedException("bummer");
}

为了扩展一点,因为我错过了一个 catch 块最初不存在的编辑:

catch 块 "eats" 异常导致它不能进一步向上移动堆栈,编译器注意到没有路径 return 是一个值或一个异常.

实际上是:

using(var objectName = <init-expression>) {
    //...
}

大致等同于:

objectName = <init-expression>;
try {
    //...
} finally {
    objectName.Dispose();
}

所以是一个try-finally-block:如果执行过程中出现问题,异常会被抛出方法外(主要是在finally部分之后已完成)。

A try-finally 但是不会创建替代代码路径:如果 try-部分 return 有问题或抛出错误,它将首先执行 finally 部分,然后抛出异常或 return 应该 returned.