Let's say we have following readonly record struct definition
public readonly record struct S(int A, int B)
{
}
Accessing A and B will result in a defensive copy since in a record the positional parameters will be auto-implemented as properties.
We could avoid it by modifying the definition like this
public readonly record struct S(int A, int B)
{
public readonly int A = A;
public readonly int B = B;
}
In this case A and B are fields, hence no side effects on access, hence no defensive copies. From my tests and understanding in both definitions the compiler synthesizes the same record specific code(Equality, HashCode generation, etc...).
I am wondering if there is a better way to avoid defensive copies with records. Definition of all the readonly fields can be cumbersome and potentially error prone. Is there any C# Syntax to achieve what I am aiming to do here or are the readonly fields the best I can do?
Also if my understanding of defensive copies and record code synthesis in this example is wrong please explain what I didn't get right.
Edit:
The Measurement:
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){}
I don't know what happened but know I redid the measurement and got similar values in both cases.
To run it just comment out the other definitions. The reason why I nested so many structs in structs is that I wanted to make sure that access of a field of a struct in a field of another struct doesn't make a copy as well.
I am sorry for the confusion. Apparently I did something wrong the first time around. There should not be any defensive copies in both cases.
There are no defensive copies here; readonly records are marked
[IsReadOnly], as are get-only properties - which tells the compiler enough that they aren't needed; you can see this in the IL, for example if we do:And compile/decompile this, we see:
Only the last option of a manually implemented type that is not
readonlyand uses manual properties: has defensive copies.For full context, we have: