繁体   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