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

Do C#10’s readonly record structs guarantee the same size and alignment of fields as the explicit implementation?

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

我喜欢 Records 具有的自动 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 是否在幕后做了任何事情,使我在上面写的内容在连续包装方面没有做我想做的事情?我不能让记录插入填充、间距或做任何奇怪的事情。

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

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;
    }
}

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