I am coming back to C# after a long time and was trying to catch up using the book C# 10 in a Nutshell.
The author there mentions that changing a property's accessor from init
to set
or vice versa is a breaking change. I can understand how changing it from set
to init
can be a breaking change, but I just can’t understand why changing it the other way around would be a breaking change.
For example:
// Assembly 1
Test obj = new(){A = 20};
// Assembly 2
class Test
{
public int A {get; init;} = 10;
}
This code in Assembly 1 should not be affected even if I change the init
property accessor to set
. Why then is this a breaking change?
This is because init
accessors are compiled into a setter with a modreq
declaration. The IL code for an int property P
might look something like this (See on SharpLab):
.method public hidebysig specialname
instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_P (
int32 'value'
) cil managed
{
...
}
Notice the token modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit)
.
A normal setter does not generate this modreq
.
On the caller's side, the call instruction must supply the modreq
declaration as part of the signature of the thing to call, if and only if a modreq
exists on that method. Therefore, the call to an init
accessor would look like this:
callvirt instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) SomeClass::set_P(int32)
and not just
callvirt instance void SomeClass::set_P(int32)
If you changed to a setter, then all the calls to the init
accessor must be changed to remove the modreq
, or else it would not resolve the method correctly. Hence, this is a breaking change.
As for why modreq
is used instead of a regular attribute to mark the property, see this section in the draft spec. To summarise, this is a trade-off between binary compatibility and "what would a compiler not aware of init
accessors do". In the end they decided to sacrifice binary compatibility, so that a compiler that doesn't know about init
doesn't allow code that sets the property.