说我有以下代码:
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规范,但无法找到我自己)
答案 0 :(得分:26)
实际上,这是out
意味着什么的一个方面;首先,请注意out
并不存在 - 我们只需要考虑ref
(out
只是ref
并在编译器中进行一些“明确赋值”调整) 。 ref
表示“传递此地址” - 如果我们通过地址更改值,则显示立即 - 毕竟,更新堆栈上的内存 Main
。它不能抽象这个(延迟写入),因为该值可能是,例如,一些超大的结构使用ref
专门用于避免在堆栈上复制它(XNA中广泛使用的一种方法)等)。
答案 1 :(得分:7)
答案 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()
参数始终已分配。实践中