在 Try-Catch-Finally 中访问之前,C# 局部变量可能未初始化

C# Local variable might not be initialized before accessing, in a Try-Catch-Finally

也许我遗漏了一些明显的东西。考虑以下代码:

string str;
try
{
    str = "";
}
catch (Exception)
{
    str = "";
}
finally
{
    Console.WriteLine(str);
}
Console.WriteLine(str); //this compiles

此处编译器显示众所周知的错误 CS0165:使用未分配的局部变量 'str'。我知道我可以通过 string str = null 来修复它。但是,str在哪个执行路径可能没有被初始化?

提供另一种与线程无关的方式:编译器以这种方式运行因为这是指定语言的方式

来自 ECMA C# 5 规范第 10.4.4.16 节:

Try-catch-finally statements

Definite assignment analysis for a try-catch-finally statement of the form:

try try-block
catch ( … ) catch-block-1
…
catch ( … ) catch-block-n
finally finally-block

is done as if the statement were a try-finally statement enclosing a try-catch statement:

try {
  try try-block
  catch ( … ) catch-block-1
   …
  catch ( … ) catch-block-n
}
finally finally-block

那么 try-finally 语句在明确赋值方面是如何工作的呢?那是在第 10.4.4.16 节中:

For a try statement stmt of the form:

try try-block finally finally-block

  • ...
  • The definite assignment state of v at the beginning of finally-block is the same as the definite assignment state of v at the beginning of stmt.

那么这对你来说意味着什么?在语句的开头,您的变量 str 未明确分配...因此根据这些规则, 在 [=16= 的开头未明确分配]块。

现在,为什么要这样设计语言?这是一个稍微不同的问题。我不认为这与线程有任何关系。该语言通常假定 anything 可以抛出异常。变量明确赋值的唯一方法是为其赋值 并且该赋值在不抛出异常的情况下完成 。任何即使在赋值前发生异常也可能发生的代码路径都不能认为是对变量的明确赋值。

举个简单的例子,假设我们将您的代码更改为:

string str;
try
{
    str = MethodThatThrowsAnException();
}
catch (Exception)
{
    str = MethodThatThrowsAnException();
}
finally
{
    Console.WriteLine(str);
}

在这一点上,str 没有明确分配似乎并不奇怪。只是因为它正在分配一个字符串文字,所以它看起来不可能失败。但我可以想象,如果这是第一次看到该字符串常量,并且它需要分配一个 String 对象,那么即使分配一个字符串文字也会失败……分配可能会失败。然后还有所有其他可以抛出异常的方式,包括线程被中止。

所有这些意味着:

  • try 块中的第一条语句可以抛出异常
  • catch 块中的第一条语句可以抛出异常

在那种情况下——无论如何发生,无论它是否与线程有关(例如,它可能是分配失败)——你不会执行任何分配给 str , 所以它没有定义的值来读取。