C#10 的只读记录结构是不是保证与显式实现相同的字段大小和对齐方式?

Posted

技术标签:

【中文标题】C#10 的只读记录结构是不是保证与显式实现相同的字段大小和对齐方式?【英文标题】:Do C#10’s readonly record structs guarantee the same size and alignment of fields as the explicit implementation?C#10 的只读记录结构是否保证与显式实现相同的字段大小和对齐方式? 【发布时间】:2021-12-05 00:12:07 【问题描述】:

我做的事情需要有连续的数据。现在有了 C# 10,我们可以做到public readonly record struct

我喜欢拥有记录所具有的自动 ToString 功能等,因此为我完成这项功能非常棒。

因此,以下是否等效?

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public readonly struct MyVector

    public readonly float X;
    public readonly float Y;
    public readonly float Z;

    public MyVector(float x, float y, float z)
    
        X = x;
        Y = y;
        Z = z;
    

与简洁的 C# 10 版本相比

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public readonly record struct MyVectorRecord(float X, float Y, float Z)


或者有没有我会不小心踩到的地雷?我的意思是record 在幕后所做的任何事情使我上面写的内容在连续包装方面不符合我的要求吗?我不能让记录插入填充、间距或做任何奇怪的事情。

我没有使用带有记录结构的向量类,而是将其用于说明目的。您可以忽略诸如“浮点相等比较”之类的事情,因为我只对是否可以将其传递给期望 X/Y/Z 的连续序列的库感兴趣。

【问题讨论】:

它仍然是一个结构。记录不是一种新类型,它们是现有类型的新行为。事实上,没有 readonly 关键字,record struct 是可变的,就像任何其他 struct PS:为什么不使用Vector3,它可以让您使用 SIMD 操作? @PanagiotisKanavos 双打有 Vector3 吗?有一些非常方便的功能,比如可以根据需要访问添加属性,或者像 Vec3 v = (1, 2, 3); 这样的构造函数重载。我有一个担心是 here 说“通常使用 SIMD 的性能优势取决于特定场景,在某些情况下,它甚至可能比更简单的非 SIMD 等效代码执行得更差。”并不是说这些都是很好的理由,我非常愿意相信改变。 【参考方案1】:

record 不是新类型,它是应用于引用类型和值类型的特定行为。该结构仍然是一个结构。您可以在sharplab.io 进行测试,以查看编译器在每种情况下生成的代码。

但记录使用属性,而不是原始字段,因此您只能将结构与属性进行比较以记录结构。这就是重要的区别

这个结构:

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public readonly struct MyVectorRecord2
 
    public float X get; 
    public float Y get; 
    public float Z get;
    
     public MyVectorRecord2(float x, float y, float z)
    
        X = x;
        Y = y;
        Z = z;
    

生产

[StructLayout(LayoutKind.Sequential, Pack = 4)]
[IsReadOnly]
public struct MyVectorRecord2

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly float <X>k__BackingField;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly float <Y>k__BackingField;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly float <Z>k__BackingField;

    public float X
    
        [CompilerGenerated]
        get
        
            return <X>k__BackingField;
        
    

    public float Y
    
        [CompilerGenerated]
        get
        
            return <Y>k__BackingField;
        
    

    public float Z
    
        [CompilerGenerated]
        get
        
            return <Z>k__BackingField;
        
    

    public MyVectorRecord2(float x, float y, float z)
    
        <X>k__BackingField = x;
        <Y>k__BackingField = y;
        <Z>k__BackingField = z;
    

同时记录

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public readonly record struct MyVectorRecord(float X, float Y, float Z)


产生:

[StructLayout(LayoutKind.Sequential, Pack = 4)]
[IsReadOnly]
public struct MyVectorRecord : IEquatable<MyVectorRecord>

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly float <X>k__BackingField;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly float <Y>k__BackingField;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly float <Z>k__BackingField;

    public float X
    
        [CompilerGenerated]
        get
        
            return <X>k__BackingField;
        
        [CompilerGenerated]
        init
        
            <X>k__BackingField = value;
        
    

    public float Y
    
        [CompilerGenerated]
        get
        
            return <Y>k__BackingField;
        
        [CompilerGenerated]
        init
        
            <Y>k__BackingField = value;
        
    

    public float Z
    
        [CompilerGenerated]
        get
        
            return <Z>k__BackingField;
        
        [CompilerGenerated]
        init
        
            <Z>k__BackingField = value;
        
    

    public MyVectorRecord(float X, float Y, float Z)
    
        <X>k__BackingField = X;
        <Y>k__BackingField = Y;
        <Z>k__BackingField = Z;
    

    public override string ToString()
    
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append("MyVectorRecord");
        stringBuilder.Append("  ");
        if (PrintMembers(stringBuilder))
        
            stringBuilder.Append(' ');
        
        stringBuilder.Append('');
        return stringBuilder.ToString();
    

    private bool PrintMembers(StringBuilder builder)
    
        builder.Append("X = ");
        builder.Append(X.ToString());
        builder.Append(", Y = ");
        builder.Append(Y.ToString());
        builder.Append(", Z = ");
        builder.Append(Z.ToString());
        return true;
    

    public static bool operator !=(MyVectorRecord left, MyVectorRecord right)
    
        return !(left == right);
    

    public static bool operator ==(MyVectorRecord left, MyVectorRecord right)
    
        return left.Equals(right);
    

    public override int GetHashCode()
    
        return (EqualityComparer<float>.Default.GetHashCode(<X>k__BackingField) * -1521134295 + EqualityComparer<float>.Default.GetHashCode(<Y>k__BackingField)) * -1521134295 + EqualityComparer<float>.Default.GetHashCode(<Z>k__BackingField);
    

    public override bool Equals(object obj)
    
        return obj is MyVectorRecord && Equals((MyVectorRecord)obj);
    

    public bool Equals(MyVectorRecord other)
    
        return EqualityComparer<float>.Default.Equals(<X>k__BackingField, other.<X>k__BackingField) && EqualityComparer<float>.Default.Equals(<Y>k__BackingField, other.<Y>k__BackingField) && EqualityComparer<float>.Default.Equals(<Z>k__BackingField, other.<Z>k__BackingField);
    

    public void Deconstruct(out float X, out float Y, out float Z)
    
        X = this.X;
        Y = this.Y;
        Z = this.Z;
    

最后,这个

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public readonly struct MyVector

    public readonly float X;
    public readonly float Y;
    public readonly float Z;

    public MyVector(float x, float y, float z)
    
        X = x;
        Y = y;
        Z = z;
    

保持不变,除了IsReadOnly 属性。

[StructLayout(LayoutKind.Sequential, Pack = 4)]
[IsReadOnly]
public struct MyVector

    public readonly float X;

    public readonly float Y;

    public readonly float Z;

    public MyVector(float x, float y, float z)
    
        X = x;
        Y = y;
        Z = z;
    

最大的区别在于具有字段的结构和具有公共属性的结构。之后,与具有属性的结构相比,record struct 仅包含额外的方法。

【讨论】:

以上是关于C#10 的只读记录结构是不是保证与显式实现相同的字段大小和对齐方式?的主要内容,如果未能解决你的问题,请参考以下文章

C#'var'关键字与显式定义的变量[重复]

Laravel 急切加载与显式连接

在 C# 中实现接口与显式实现接口 [重复]

隐式线程与显式线程性能[关闭]

JDK并发包温故知新系列—— 显式锁与显式条件

定位new表达式与显式调用析构函数