record
isn't a new type, it's specific behavior applied to reference and now value types. The struct remains a struct. You can test this at sharplab.io, to see the code generated by the compiler in each case.
A record uses properties though, not raw fields, so you can only compare structs with properties to record structs. That's the important difference
This struct:
[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;
}
}
produces
[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;
}
}
While the record
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public readonly record struct MyVectorRecord(float X, float Y, float Z)
{
}
produces:
[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;
}
}
Finally, this
[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;
}
}
Remains unchanged, apart from the IsReadOnly
attribute.
[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;
}
}
The big difference is between structs with fields and structs with public properties. After that, a record struct
contains only extra methods compared to a struct with properties.
readonly
keyword arecord struct
is mutable, just like any otherstruct
– TalcVector3
which would allow you to use SIMD operations? – TalcVec3 v = (1, 2, 3);
. There is one worry I have which is here saying "In general the performance benefit of using SIMD varies depending on the specific scenario, and in some cases it can even perform worse than simpler non-SIMD equivalent code." Not saying these are great reasons, and I'm more than open to be convinced of changing. – Shiner