为什么将 ref 结构体传递给方法实际上传递了 ref 结构体的副本呢?

huangapple go评论65阅读模式
英文:

Why passing ref struct to methods actually passes a copy of a ref struct?

问题

当我试图传递一个看起来像这样的 ref 结构体时

public ref struct BufferWriter
{
    readonly Buffer* _pb;
    public int WrittenBytes;
    //...
    public bool Append(ReadOnlySpan<byte> buffer)
    {
        //...
        WrittenBytes += buffer.Length;
        //...
    }
}

给一个方法(在下面的例子中是 Write100U8Chars(BufferWriter, Object)),实际上并没有通过引用传递结构体,而是通过值传递:

protected override bool Serialize(object obj)
{
    //...
    fixed (Buffer* pb = &doc_body)
    {
        using (BufferWriter writer = new BufferWriter(pb))
        {
            writer.Append("UTF8 str"U8);
            // pb->Bytes is now 8
            // writer.WrittenBytes is now also 8
            Write100U8Chars(writer, obj);
            // pb->Bytes is now 108
            // But writer.WrittenBytes is still 8!
        }
    }
    //...
}

// A method that always calls BufferWriter.Append passing 100 UTF-8 chars (aka bytes) 
protected abstract bool Write100U8Chars(BufferWriter writer, object obj);

由于 using 块在某种程度上是只读上下文,根据我的猜测,它会强制执行“防御性拷贝”,因此我尝试移除 using 块/指令:

fixed (Buffer* pb = &doc_body)
{
    BufferWriter writer = new BufferWriter(pb)
    writer.Append("UTF8 str"U8);
    // writer.WrittenBytes is now 8
    Write100U8Chars(writer, obj);
    // No use of removing using: writer.WrittenBytes is still 8
}

BufferWriter 设为 ref readonly struct 没有任何区别。

writer 传递到方法 Write100U8Chars(BufferWriter, Object) 中并使用 in 参数仍然没有任何区别。

如何使一个 ref struct 被传递到抽象方法中时保持引用而不是像普通非 ref 结构体那样被传递值?

英文:

When I'm trying to pass a ref struct that looks like, say

public ref struct BufferWriter
{
    readonly Buffer* _pb;
    public int WrittenBytes;
    //...
    public bool Append(ReadOnlySpan&lt;byte&gt; buffer)
    {
        //...
        WrittenBytes += buffer.Length;
        //...
    }
    
}

to a method (Write100U8Chars(BufferWriter, Object) in the following example), a struct is not actually passed by a reference, but by the value:

protected override bool Serialize(object obj)
{
    //...
    fixed (Buffer* pb = &amp;doc_body)
    {
        using (BufferWriter writer = new BufferWriter(pb))
        {
            writer.Append(&quot;UTF8 str&quot;U8);
            // pb-&gt;Bytes is now 8
            // writer.WrittenBytes is now also 8
            Write100U8Chars(writer, obj);
            // pb-&gt;Bytes is now 108
            // But writer.WrittenBytes is still 8!
        }
    }
    //...
}

// A method that always calls BufferWriter.Append passing 100 UTF-8 chars (aka bytes) 
protected abstract bool Write100U8Chars(BufferWriter writer, object obj);

Because of the using block being kind of read-only context which, as I supposed, enforces making "defensive copies", I've attempted to remove the using block/directive:

fixed (Buffer* pb = &amp;doc_body)
{
    BufferWriter writer = new BufferWriter(pb)
    writer.Append(&quot;UTF8 str&quot;U8);
    // writer.WrittenBytes is now 8
    Write100U8Chars(writer, obj);
    // No use of removing using: writer.WrittenBytes is still 8
}

Making BufferWriter a ref readonly struct makes no difference.

Passing a writer local to a method Write100U8Chars(BufferWriter, Object) with in parameter still makes no difference.

How can I make a ref struct be passed into abstract method by reference and not by value like it is the case with ordinary non-ref structs?

答案1

得分: 4

保护的抽象方法,写入100个U8字符,传递 writer 的方式是按值传递。如果要按引用传递,应该是:

protected abstract bool Write100U8Chars(ref BufferWriter writer, object obj);

据我理解,在结构声明中使用 ref 是为了定义值类型如何分配(仅堆栈),但它仍将像常规结构一样按值传递。

英文:
protected abstract bool Write100U8Chars(BufferWriter writer, object obj);

This call is passing writer by value. If you want to pass it by reference, it should be:

protected abstract bool Write100U8Chars(ref BufferWriter writer, object obj);

As far as I understand it, the ref in the struct declaration is used to define how the value type can be allocated (only stack) but it will still behave as a regular struct and get passed by value by default.

答案2

得分: 2

ref struct 的目的是允许结构体使用只能存在于堆栈上的类型。典型示例是 Span<T>,但C# 11也允许引用字段。主要观点是如果将结构体存储在堆上,可能会违反内存安全性。

据我所知,您的结构体似乎不需要这个,因为指针应该可以在常规结构体中使用。这确实可能会违反内存安全性,但指针需要使用不安全代码,因此内存安全性已经不存在。

如果您想传递对结构体的引用,需要在方法中声明,即void MyMethod(ref BufferWriter)

英文:

The purpose of ref struct is to allow the struct to use types that can only ever be permitted to exist on the stack. The typical example is Span&lt;T&gt;, but C# 11 also allow ref fields. The main point is that memory safety can be be violated if the struct is stored on the heap.

As far as I can tell, your struct does not need this, since a pointer should be possible to use in a regular struct. This does potentially violate memory safety, but pointers need unsafe code anyway, so memory safety is out the window already.

If you want to pass a reference to a struct you need to declare that in the method, i.e. void MyMethod(ref BufferWriter)

huangapple
  • 本文由 发表于 2023年3月9日 22:05:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75685686.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定