[英]C# 11 escape rules for ref parameters: ref int vs Span<int>
為什么下面的代碼在C# 11編譯不通過?
// Example 1 - fails
class C {
public Span<int> M(ref int arg) {
Span<int> span;
span = new Span<int>(ref arg);
return span;
}
}
它產生兩個編譯錯誤:
錯誤 CS9077:無法通過 ref 參數按引用“arg”返回參數; 它只能在返回語句中返回。
錯誤 CS8347:無法在此上下文中使用“Span.Span(ref int)”的結果,因為它可能會在其聲明 scope 之外公開參數“reference”引用的變量。
它們對我來說都沒有意義:我的代碼不會嘗試通過 ref 參數返回arg
,並且它不能在聲明 scope 之外公開arg
引用的變量。
通過對比,下面兩段代碼編譯成功:
// Example 2 - succeeds
class C {
public Span<int> M(ref int arg) {
Span<int> span = new Span<int>(ref arg);
return span;
}
}
// Example 3 - succeeds
class C {
public Span<int> M(Span<int> arg) {
Span<int> span;
span = new Span<int>(ref arg[0]);
return span;
}
}
我的直覺是Span<int>
內部包含一個int
類型的 ref 字段,因此轉義規則對於上面的示例 1 和示例 3 應該是一樣的(顯然,它們不是)。
我用一個 ref 結構做了一個類似的實驗,它明確地包含一個 ref 字段:
ref struct S {
public ref int X;
}
現在,以下代碼無法編譯:
// Example 4 - fails
class C {
public S M(ref int arg) {
S instance;
instance.X = ref arg;
return instance;
}
}
它會產生以下錯誤,這至少對我來說更有意義:
錯誤 CS9079:無法將“arg”引用分配給“X”,因為“arg”只能通過返回語句轉義當前方法。
通過對比,下面兩段代碼編譯成功(上面有S
的定義):
// Example 5 - succeeds
class C {
public S M(ref int arg) {
S instance = new S() { X = ref arg };
return instance;
}
}
// Example 6 - succeeds
class C {
public S M(S arg) {
S instance;
instance.X = ref arg.X;
return instance;
}
}
特別是,如果arg
只能通過 return 語句轉義當前方法,如上面示例 4 的錯誤消息所示,而示例 6 中的arg.X
是否同樣如此?
我試圖在低級別結構改進文檔中找到答案,但我失敗了。 此外,該文檔頁面似乎在幾個地方自相矛盾。
你確定你使用的是 C# 11 嗎? 將 linqpad 與 .Net 7 一起使用,您的“編譯失敗”示例對我來說效果很好:
更新:如果使用每日構建的 Rosyln 編譯器,則無法編譯
我的新假設是規范實際上變得更嚴格......並且 ex1 和 ex2 都應該失敗,但他們沒有考慮 ex2 語法,它沒有在應該觸發的時候觸發(由於 Marc G 指出的原因)所以可能是值得的就此提交錯誤報告:-)
它是 scope 的問題。看這個更明確的例子:
public void M()
{
Span<int> spanOuter;
{
int answer = 42;
spanOuter = new Span<int>(ref answer); // Compiler error
}
Console.WriteLine(spanOuter[0]); // Would access answer 42 which
// is already out of scope
}
創建的new Span<int>()
具有比變量spanOuter
。 您不能將跨度分配給具有更寬 scope 的另一個跨度,因為這可能意味着它們持有的引用數據在它們不再存在后被訪問。 在此示例中,在訪問spanOuter[0]
之前, answer
變量超出 scope。
讓我們刪除花括號:
public void M()
{
Span<int> spanOuter;
int answer = 42;
spanOuter = new Span<int>(ref answer); // Compiler error
Console.WriteLine(spanOuter[0]);
}
現在這在理論上應該可行,因為answer
變量仍在 Conole.WriteLine 的Conole.WriteLine
中。 編譯器仍然不喜歡它。 盡管沒有花括號,但spanOuter
變量的 scope 仍然比new Span<int>()
表達式更寬,因為它的聲明發生在前一行。
在檢查 scope 的寬度時,編譯器似乎非常嚴格,而 scope 的差異僅僅是因為單獨的變量聲明似乎足以不允許賦值。
即使我們在一開始就移動了answer
變量,使其基本上與參數具有相同的 scope,這仍然是不允許的。
public void M()
{
int answer = 42;
Span<int> spanOuter;
spanOuter = new Span<int>(ref answer); // Compiler error
Console.WriteLine(spanOuter[0]);
}
對於此檢查,編譯器似乎將 arguments 視為局部變量。 我同意編譯器可以更聰明一點,查看引用數據的精確 scope 並允許更多情況,但它就是不這樣做。
具體來說,當編譯器看到目標跨度變量未初始化時,編譯器似乎有特殊處理。
public void M(ref int a)
{
int answer = 42;
Span<int> spanNull = null;
Span<int> spanImplicitEmpty;
Span<int> spanExplicitEmpty = Span<int>.Empty;
Span<int> spanInitialized = new Span<int>(ref answer);
Span<int> spanArgument = new Span<int>(ref a);
spanNull = spanArgument; // Compiler Error
spanExplicitEmpty = spanArgument; // Compiler Error
spanImplicitEmpty = spanArgument; // Compiler Error
spanInitialized = spanArgument; // Works
}
使用返回值時也是如此:
public Span<int> M(ref int a)
{
int answer = 42;
Span<int> spanNull = null;
Span<int> spanImplicitEmpty;
Span<int> spanExplicitEmpty = Span<int>.Empty;
Span<int> spanInitialized = new Span<int>(ref answer);
Span<int> spanInitializedAndThenNull = new Span<int>(ref answer);
spanInitializedAndThenNull = null;
Span<int> spanArgument = new Span<int>(ref a);
spanNull = spanArgument; // Compiler Error
spanExplicitEmpty = spanArgument; // Compiler Error
spanImplicitEmpty = spanArgument; // Compiler Error
spanInitialized = spanArgument; // Works
spanInitializedAndThenNull = spanArgument; // Works
return spanArgument;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.