簡體   English   中英

為什么 C# struct 方法不能返回對字段的引用,但非成員方法可以?

[英]Why can't a C# struct method return a reference to a field, but a non-member method can?

下面是一個 struct 的實例方法的例子,它試圖返回一個只讀 ref 到結構的實例字段:

struct Foo
{
    internal int _x;

    public ref readonly int MemberGetX() => ref _x;
    //                                          ^^^
    // Error CS8170: Struct members cannot return 'this' or other instance members by reference
}

這會產生錯誤 CS8170 Struct members cannot return 'this' or other instance members by reference 但是,使用擴展方法做同樣的事情不會產生錯誤:

static class FooExtensions
{
    public static ref readonly int ExtensionGetX( this in Foo foo )
    {
        return ref foo._x;
    }
}

相關問題的答案為什么 C# 結構不能返回對其成員字段的引用? 討論為什么語言不允許第一種情形的原因,但鑒於這些原因,為什么第二種情況允許它我不清楚。


更新:

這是一個不使用readonly的完整示例,還顯示了一個非擴展方法,並演示了用法:

struct Foo
{
    internal int _x;

    // Can't compile, error CS8170
    // public ref int GetXRefMember() => ref _x;

    public int X => _x;
}

static class FooExtensions
{
    public static ref int GetXRefExtension( this ref Foo foo )
    {
        return ref foo._x;
    }

    public static ref int GetXRef( ref Foo foo )
    {
        return ref foo._x;
    }
}

class Program
{
    static void Main( string[] args )
    {
        var f = new Foo();
        Console.WriteLine( f.X );
        f.GetXRefExtension() = 123;
        Console.WriteLine( f.X );

        // You can also do it without using an extension method, but the caller is required to specify ref:
        FooExtensions.GetXRef( ref f ) = 999;
        Console.WriteLine( f.X );

        /* Output:
         * 0
         * 123
         * 999
         */
    }
}

有趣的是,擴展方法會默默地“添加” ref ,當正常調用需要調用者顯式地將ref添加到參數時,我想說清楚並防止錯誤。

我認為這包含在ref-readonly 提案中,安全返回規則部分。

從結構成員返回“this”是不安全的

這個你已經知道了。 您可以在此處閱讀更多有關為什么不允許這樣做的信息 簡而言之 - 允許它“污染任何在本地值類型上調用的 ref 返回方法”,因此使結構上的方法的所有ref 返回都不是“安全返回”,因為它們可能包含對 this 的引用。

ref/in 參數可以安全返回

只要接收者可以安全返回,實例結構字段就可以安全返回

這涵蓋了靜態方法案例。 foo參數可以安全返回(因為in ),因此, foo._x可以安全返回,它是本身可以安全返回的結構實例的字段。

如果作為形式參數傳遞給該方法的所有引用/輸出都可以安全返回,則從另一個方法返回的引用是安全的。

這可以防止上述靜態方法出現問題。 它使以下無效:

public static ref readonly int ExtensionGetX(in Foo foo) {
    return ref foo._x;
}

static ref readonly int Test() {
    var s = new Foo();
    s._x = 2;
    // fails to compile
    return ref ExtensionGetX(s);
}

因為s不能安全返回,我們從ExtensionGetX得到的 ref 也不能安全返回,所以我們不能泄漏指向作用域外的局部變量的指針。

所以簡而言之 - 它是允許的,因為它是安全的,並且沒有禁止從結構成員方法將 ref 返回到“this”的特定缺點。

更新。 我認為對您問題的更新不會改變我的答案。 上面提到的“安全返回”規則保持不變。 你改變inref ,但ref參數也安全返回。 它的實例字段也是如此。 但是如果你讓參數不安全返回:

public static ref int GetXRef(Foo foo)
{
    return ref foo._x;
}

然后它不會編譯。

您還認為(在評論中)“您不能將返回的引用存儲為局部變量”,但事實並非如此:

ref int y = ref FooExtensions.GetXRef(ref f);
y = 10;
// print 10
Console.WriteLine(f.X);

因此,無論是否只讀, inref - 安全返回規則確保在這種情況下返回 ref struct 成員是安全的,同時允許從 struct local 方法返回對this引用會產生不良后果,強制處理所有返回的所有 ref 值struct 成員返回不安全。

如果 struct member 可以將 ref 返回給this則不可能的小例子:

public ref int GetXRefMember(ref int y) => ref y;

static ref int Test(ref int y) {
    var x = new Foo();
    // safe to return, but won't be if ref to `this` can
    // ever be returned from struct local method
    return ref x.GetXRefMember(ref y);
}

另一個答案已經解釋了為什么需要此編譯器錯誤,以及它如何防止您意外保留懸空引用。

作為一種快速而骯臟的解決方法,您可以使用unsafe fixed結構來抑制此錯誤:

struct Foo
{
    internal int _x;

    public unsafe ref readonly int MemberGetX()
    {
        fixed (int* ptr = &_x)
            return ref *ptr;
    }
}

但是現在您有責任確保對Foo的本地或臨時實例的字段的引用不會超出其范圍。 編譯器不再照顧你,你就靠自己了。

但是這種方法只適用於非托管類型的字段(即完全由它們構造的原始類型和結構)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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