输出参数和异常

40

假设我有以下代码:

    static void Fjuk(out string str)
    {
        str = "fjuk!";
        throw new Exception();
    }

    static void Main(string[] args)
    {
        string s = null;
        try
        {
            Fjuk(out s);
        }
        catch (Exception)
        {
            Console.WriteLine(s ?? "");
        }
    }

当我测试它时,在catch块中使用的s已被初始化为"fjuk!"。
这是规范保证的还是与实现有关?(我已经搜索了C# 3规范,但自己找不到)


我不确定规范如何,但这肯定是我期望的。我期望成员变量、属性等的初始化在您的catch块中仍然可用。 - Mr Moose
1
需要Eric Lippert的时候他在哪里呢... :) - jb.
1
@jb。MSDN有什么问题吗? - gdoron
5
为什么要读二手教科书,而不是直接向老师学习呢? - jb.
5个回答

32
基本上,这就是out的一个方面;首先要注意的是,out并不存在——我们只需要考虑refout只是在编译器中进行了一些“明确赋值”调整的ref)。ref的意思是“传递此变量的地址”,如果我们通过地址更改值,那么它会立即显示出来——毕竟,它正在更新Main堆栈上的内存。它无法抽象化这个过程(以延迟写入),因为该值可能是某个超大型结构体,而使用ref专门用于避免在堆栈上复制它(这种方法在XNA等中广泛使用)。

8
因为out参数会使用参数的内存地址改变其值,所以它是“保证”的。

out关键字会导致按引用传递参数。这类似于ref关键字,不同之处在于ref要求在传递之前对变量进行初始化。

来自MSDN

6

如果该方法抛出异常,则不保证输出参数被设置。如果该方法没有抛出异常,则保证输出参数被设置。

在你的情况下,该方法将始终设置输出参数,但是编译器并不以这种方式分析方法的代码。如果该方法因异常而退出,则仍然认为输出参数未被明确设置。

异常处理程序中的代码不依赖于通过方法调用设置变量,因为您在创建变量时已经设置了它。如果在创建变量时不设置变量,则异常处理程序无法使用变量,因为不能保证其已设置:

string s;
try {
  Fjuk(out s);
  Console.WriteLine(s); // here the variable is guaranteed to be set
} catch (Exception) {
  Console.WriteLine(s); // here it's not, so this won't compile
}

是的,我认为这显而易见,它不会检查被调用方法中的代码,但在我的示例中没有明确说明。感谢您指出这一点! - Niklas

3

Fjuk的角度来看,它是有保证的,但对于Main则不一定。

Fjuk中,参数设置后才会引发异常。虽然编译器、Jitter和CPU可以进行重新排序,但不会进行单个线程观察到的顺序更改的重新排序。由于单个线程可能会“注意到”异常抛出前参数是否已设置,因此保证了参数被设置。

然而,在Main中,我们不知道Fjuk实现的细节,因此在编译器分析Main时,无法依赖此特性。因此,在没有为调用之前分配值给s的情况下:

static void Main()
{
    string s;
    try
    {
        Fjuk(out s);
        Console.WriteLine(s ?? "");//fine
    }
    catch (Exception)
    {
        Console.WriteLine(s ?? "");//compiler error
    }
    Console.WriteLine(s ?? "");//compiler error
}

在调用 Fjuk 后立即使用 s 的第一次尝试是可以的,因为只有在 Fjuk 成功后才能到达那里,而如果 Fjuk 成功,则必须分配 s。然而,在第二和第三种情况下,可能会到达这些行而没有成功执行 Fjuk,由于无法通过对 Main 进行分析来判断在设置 s 之前是否可能引发异常,因此必须禁止使用 s


1

来自C#语言规范第5.1.6节输出参数

在函数成员或委托调用正常完成后,每个作为输出参数传递的变量都被认为在该执行路径中被赋值。

换句话说

  • out参数始终被赋值。
  • 任何先前的值都将被覆盖。
  • 但是,如果函数引发异常,则编译器假定它们未被分配。

在实践中

  • 您永远不需要担心这个问题。
  • 如果尝试使用未分配的变量,编译器将生成错误。
  • 因此,C#中的输出参数是完全安全的 - 如果编译通过,它们就可以工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接