假设我们有以下只读记录 struct 定义

public readonly record struct S(int A, int B)
{
   
}

由于在记录中位置参数将自动实现为属性,因此AB将导致防御副本.

我们可以通过这样修改定义来避免它

public readonly record struct S(int A, int B)
{
   public readonly int A = A;
   public readonly int B = B;
}

在这种情况下,AB是字段,因此对访问没有副作用,因此没有防御性拷贝.根据我的测试和对这两个定义的理解,编译器会合成相同的记录特定代码(Equity、HashCode生成等).

我想知道是否有更好的方法来避免带有记录的防御性副本.所有只读字段的定义可能很麻烦,而且可能容易出错.是否有任何C#语言来实现我在这里的目标,或者只读字段是我能做的最好的?

另外,如果我对本例中的防御性副本和记录代码合成的理解是错误的,请解释我没有正确理解的地方.

编辑:

衡量标准:

const int SIZE = 10000;

var sw = new Stopwatch();
A[] aArray = new A[SIZE];
M[] mArray = new M[SIZE];
for(int repeat = 0; repeat < SIZE; repeat++) aArray[repeat] = new A(new(new(new(new(new(new(new(new(new(new(new(Random.Shared.Next()))))))))))));
for(int repeat = 0; repeat < SIZE; repeat++) mArray[repeat] = new M(new(Random.Shared.Next()));
int i = 0;
sw.Start();
for(int repeat = 0; repeat < SIZE; repeat++) i =+ aArray[repeat].B.C.D.E.F.G.H.I.J.K.L.I;
sw.Stop();
System.Console.WriteLine(i);
System.Console.WriteLine(sw.Elapsed);
sw.Reset();
sw.Start();
for(int repeat = 0; repeat < SIZE; repeat++) i =+ mArray[repeat].N.I;
sw.Stop();
System.Console.WriteLine(i);
System.Console.WriteLine(sw.Elapsed);

readonly record struct A(B B){public readonly B B = B;}
readonly record struct B(C C){public readonly C C = C;}
readonly record struct C(D D){public readonly D D = D;}
readonly record struct D(E E){public readonly E E = E;}
readonly record struct E(F F){public readonly F F = F;}
readonly record struct F(G G){public readonly G G = G;}
readonly record struct G(H H){public readonly H H = H;}
readonly record struct H(I I){public readonly I I = I;}
readonly record struct I(J J){public readonly J J = J;}
readonly record struct J(K K){public readonly K K = K;}
readonly record struct K(L L){public readonly L L = L;}
readonly record struct L(int I){public readonly int I = I;}
readonly record struct M(N N){public readonly N N = N;}
readonly record struct N(int I){public readonly int I = I;}

readonly record struct A(B B){}
readonly record struct B(C C){}
readonly record struct C(D D){}
readonly record struct D(E E){}
readonly record struct E(F F){}
readonly record struct F(G G){}
readonly record struct G(H H){}
readonly record struct H(I I){}
readonly record struct I(J J){}
readonly record struct J(K K){}
readonly record struct K(L L){}
readonly record struct L(int I){}
readonly record struct M(N N){}
readonly record struct N(int I){}

我不知道发生了什么,但我知道我重做了测量,在两种情况下得到了相似的值.

要运行它,只需注释掉其他定义.我在 struct 体中嵌套这么多 struct 体的原因是,我想确保在另一个 struct 体的字段中访问一个 struct 体的字段不会产生副本.

我很抱歉给您造成了混乱.显然我第一次就做错了什么.在这两种情况下都不应该有任何防御性的副本.

推荐答案

这里没有防御副本;只读记录被标记为[IsReadOnly],get-only属性也是如此-这足以告诉编译器它们是aren't needed;你可以在IL中看到这一点,例如,如果我们这样做:

using System;

void NoCopies(in S obj)
{   // no copies because the *type* is [IsReadOnly]
    Console.WriteLine(obj.A);
    Console.WriteLine(obj.B);
}

void AlsoHasNoCopies(in T obj)
{   // no copies because the *members* are [IsReadOnly]
    Console.WriteLine(obj.A);
    Console.WriteLine(obj.B);
}

void StillNoCopies(in LazyManual obj)
{   // no copies because the *members* are [IsReadOnly]
    Console.WriteLine(obj.A);
    Console.WriteLine(obj.B);
}


void DoesHaveCopies(in BasicManual obj)
{   // finally, we have defensive copies
    Console.WriteLine(obj.A);
    Console.WriteLine(obj.B);
}


public readonly record struct S(int A, int B)
{
   
}

public record struct T(int A, int B) // not marked readonly
{
   
}

public struct LazyManual
{
    public int A {get;} // auto-marked [IsReadOnly]
    public int B {get;}
}

public struct BasicManual
{
    private int _a, _b;
    public int A => _a; // not auto-marked [IsReadOnly]
    public int B => _b;
}

编译/反编译这段代码,我们可以看到:

[CompilerGenerated]
internal class Program
{
    private static void <Main>$(string[] args)
    {
    }

    [CompilerGenerated]
    internal static void <<Main>$>g__NoCopies|0_0([In][IsReadOnly] ref S obj)
    {
        Console.WriteLine(obj.A);
        Console.WriteLine(obj.B);
    }

    [CompilerGenerated]
    internal static void <<Main>$>g__AlsoHasNoCopies|0_1([In][IsReadOnly] ref T obj)
    {
        Console.WriteLine(obj.A);
        Console.WriteLine(obj.B);
    }

    [CompilerGenerated]
    internal static void <<Main>$>g__StillNoCopies|0_2([In][IsReadOnly] ref LazyManual obj)
    {
        Console.WriteLine(obj.A);
        Console.WriteLine(obj.B);
    }

    [CompilerGenerated]
    internal static void <<Main>$>g__DoesHaveCopies|0_3([In][IsReadOnly] ref BasicManual obj)
    {
        BasicManual basicManual = obj;
        Console.WriteLine(basicManual.A);
        basicManual = obj;
        Console.WriteLine(basicManual.B);
    }
}

只有手动实现的类型的最后一个选项not readonly使用手动属性:具有防御性副本.


对于完整的背景,我们有:

[IsReadOnly]
public struct S : IEquatable<S>
{ ... }

public struct T : IEquatable<T>
{
    // ...
    public int A
    {
        [IsReadOnly]
        [CompilerGenerated]
        get
        {
            return <A>k__BackingField;
        }
        [CompilerGenerated]
        set
        {
            <A>k__BackingField = value;
        }
    }

    public int B
    {
        [IsReadOnly]
        [CompilerGenerated]
        get
        {
            return <B>k__BackingField;
        }
        [CompilerGenerated]
        set
        {
            <B>k__BackingField = value;
        }
    }
}

public struct LazyManual
{
    [CompilerGenerated]
    private readonly int <A>k__BackingField;

    [CompilerGenerated]
    private readonly int <B>k__BackingField;

    public int A
    {
        [IsReadOnly]
        [CompilerGenerated]
        get
        {
            return <A>k__BackingField;
        }
    }

    public int B
    {
        [IsReadOnly]
        [CompilerGenerated]
        get
        {
            return <B>k__BackingField;
        }
    }
}

public struct BasicManual
{
    private int _a;

    private int _b;

    public int A
    {
        get
        {
            return _a;
        }
    }

    public int B
    {
        get
        {
            return _b;
        }
    }
}

Csharp相关问答推荐

为什么.Equals(SS,StringComparison. ClientCultureIgnoreCase)在Net 4.8和6.0之间不同?

将修剪声明放入LINQ中

Polly使用泛型重试和重试包装函数

XUNIT是否使用测试数据的源生成器?

从ASP.NET Core中的枚举字段填充 Select 选项时,将默认的第一个选项添加为 Select 元素

Azure函数中实体框架核心的依赖注入

try 使用C#ASP.NET收集WMI信息时访问被拒绝,但在PowerShell中工作

在C#中,非静态接口方法的抽象和虚拟是冗余的吗?

C#按名称从类获取属性值类型<;t>;,我需要反射吗?

RX操作员使用先前值进行扫描,信号来自值本身

如何在发布NuGet包之前设置命名空间?

为什么当我try 为玩家角色设置动画时,没有从文件夹中拉出正确的图像?

如何在使用Google.Drive.apis.V3下载文件/文件夹之前压缩?

如何从SignalR获取连接客户端的域

如何在C#.NET桌面应用程序中动态更改焦点工具上的后退 colored颜色

项目参考和方法签名问题

HttpClient,上传文件时实现进度

我是否以错误的方式使用了异步延迟初始化?

无法通过服务控制台启动.NET Core 6.0服务(错误1053)

C#:我需要根据换行符拆分字符串,而不是在字符串中用双引号分隔换行符