输出参数和异常

时间:2012-01-18 07:51:23

标签: c# exception out

说我有以下代码:

    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 ?? "");
        }
    }

当我测试它时,s已初始化为“fjuk!”当它在catch区块中使用时。
这是由规范保证还是依赖于实现? (我已经搜索了C#3规范,但无法找到我自己)

5 个答案:

答案 0 :(得分:26)

实际上,这是out意味着什么的一个方面;首先,请注意out并不存在 - 我们只需要考虑refout只是ref并在编译器中进行一些“明确赋值”调整) 。 ref表示“传递此地址” - 如果我们通过地址更改值,则显示立即 - 毕竟,更新堆栈上的内存 Main 。它不能抽象这个(延迟写入),因为该值可能是,例如,一些超大的结构使用ref专门用于避免在堆栈上复制它(XNA中广泛使用的一种方法)等)。

答案 1 :(得分:7)

“保证”,因为out参数会使用参数的memory address更改值。

  

out关键字导致参数通过引用传递。这类似于ref关键字,除了ref要求在传递之前初始化变量。

来自MSDN

答案 2 :(得分:4)

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

在您的情况下,该方法将始终设置输出参数,但编译器不会以这种方式分析方法的代码。如果方法以异常退出,则仍不认为输出参数已明确设置。

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

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
}

答案 3 :(得分:2)

Fjuk但不是Main的角度来保证。

Fjuk中,在设置参数后抛出异常。虽然可以通过编译器,抖动和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
}

在调用s后立即第一次尝试使用Fjuk是正常的,因为只有Fjuk成功,并且Fjuk成功,s成功必须分配{1}}。但是,在第二种情况和第三种情况下,可以在没有Fjuk成功的情况下到达那些行,并且因为在Main被设置之前分析s是否可以抛出异常,必须禁止使用s

答案 4 :(得分:0)

来自C# Language Specification部分5.1.6输出参数

  

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

换句话说

  • final class DecompressTgzCodec extends CompressionCodec { override def getDefaultExtension: String = ".tgz" override def createOutputStream(out: OutputStream): CompressionOutputStream = ??? override def createOutputStream(out: OutputStream, compressor: Compressor): CompressionOutputStream = ??? override def createCompressor(): Compressor = ??? override def getCompressorType: Class[_ <: Compressor] = ??? override def createInputStream(in: InputStream): CompressionInputStream = { new TarDecompressorStream(new TarArchiveInputStream(new GzipCompressorInputStream(in))) } override def createInputStream(in: InputStream, decompressor: Decompressor): CompressionInputStream = createInputStream(in) // we do not create any decompressor override def createDecompressor(): Decompressor = null // we do not create any decompressor override def getDecompressorType: Class[_ <: Decompressor] = null // see https://www.conductor.com/nightlight/how-to-build-a-speedy-custom-compression-codec-for-hadoop/ final class TarDecompressorStream(in: TarArchiveInputStream) extends DecompressorStream(in) { def updateStream(): Unit = { // still have data in stream -> done if (in.available() <= 0) { // create stream content from following tar elements one by one in.getNextTarEntry } } override def read: Int = { checkStream() updateStream() in.read() } override def read(b: Array[Byte], off: Int, len: Int): Int = { checkStream() updateStream() in.read(b, off, len) } override def resetState(): Unit = { } } } ... // register the new codec: val conf = new SparkConf() conf.set("spark.hadoop.io.compression.codecs", classOf[DecompressTgzCodec].getName) val spark = SparkSession .builder() .master(...) .config(conf) .appName("Test") .getOrCreate() 参数始终已分配。
  • 以前的任何值都会被覆盖。
  • 但如果函数抛出异常,则编译器假定它们未被分配。

实践中

  • 你永远不必担心这个。
  • 如果尝试使用未分配的变量,编译器将生成错误。
  • 所以C#中的 out参数是完全安全的 - 如果他们编译,它们就可以工作。