Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is changing a property from "init" to "set" a binary breaking change?

Tags:

c#

properties

abi

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?

like image 508
MS Srikkanth Avatar asked Nov 17 '25 03:11

MS Srikkanth


2 Answers

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.

like image 148
Sweeper Avatar answered Nov 18 '25 19:11

Sweeper


This is documented in the design notes, specifically in the section titled "Breaking Changes".

The emit strategy for init property accessors must choose between using attributes or modreqs when emitting during metadata. These have different trade offs that need to be considered.

Annotating a property set accessor with a modreq declaration means CLI compliant compilers will ignore the accessor unless it understands the modreq. That means only compilers aware of init will read the member. Compilers unaware of init will ignore the set accessor and hence will not accidentally treat the property as read / write.

The downside of modreq is init becomes a part of the binary signature of the set accessor. Adding or removing init will break binary compatbility of the application.

And the final decision was:

Given there was also no significant support for removing init to be a binary compatible change it made the choice of using modreq straight forward.

So they used modreq in the implementation, with the result that replacing an init with set will be a binary breaking change.

There is more discussion about modreq here.

If you really want to know more about modreq then you can look for some details in the CLI spec.

like image 38
Matthew Watson Avatar answered Nov 18 '25 17:11

Matthew Watson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!