簡體   English   中英

C# ref 參數的 11 條轉義規則:ref int vs Span<int></int>

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM