# init-only properties

[Init-only properties](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/init) are a new addition to C# 9. The purpose of this blog post is to look into how it is represented in IL, as well to see how it behaves from [IL verification](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/init#il-verification)'s point of view.

## What is an init-only property?

Properties, readonly properties and properties with private setter have been around for a while in C#. One use-case was still missing: setting properties with object initializers that are readonly. Previously these properties had to be read-write or values had to be passed through constructors, which involved writing a good set of constructors. The following code snippet shows an example of an init-only property. ```State``` class has a ```bool``` 'On' property, which is an init-only property.

```csharp
var data = new State { On = true };
Console.WriteLine(data.On);

public class State
{
  public bool On { get; init; }
}
```

### IL

Using [ILSpy](https://github.com/icsharpcode/ILSpy) or ```ildasm``` we can read the IL instructions output by the C# compiler.

```
.property instance bool On()
{
  .get instance bool State::get_On()
  .set instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) State::set_On(bool)
}
```
The setter property has a ```modreq``` modifier requiring a ```IsExternalInit``` type, while the getter still looks the same as before. ```modreq``` seems an interesting keyword, there is not much official documentation around it at the time of writing this post.

How does the IL look like for an object initializer?

```
IL_0000: newobj instance void State::.ctor()
IL_0005: dup
IL_0006: ldc.i4.1
IL_0007: callvirt instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit)State::set_On(bool)
IL_000c: dup
IL_000d: ldc.i4.1
IL_000e: callvirt instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit)State::set_Enabled(bool)
IL_0013: stloc.0
```

With object initialization, when a new object is created, it is being *duplicated* on the evaluation stack until all its properties are set. In the example above 2 init-only properties are set, *On* and *Enabled*. Once all the properties are set, the object is [stored](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.stloc?view=net-5.0) in a local variable list, so it can be used later on and passed by reference.

Editing C# code, it is clear that the C# compiler enforces the rules which makes this property init-only in the code editor and at build time.

## Serialization

Following the rules of the C# compiler, the runtime would need to adhere the same set of rules when populating init-only properties. In the discussion of the feature, IL verification has been discussed, with the runtime to restrict when a property is set. Running the below example, we can confirm that serialization today works without an issue.

```csharp
var initalData = new State() { On = true };
var serialized = JsonSerializer.Serialize(initalData);
var deserialized = JsonSerializer.Deserialize<State>(serialized);
Console.WriteLine(deserialized.On); // True
Console.WriteLine(deserialized == initalData); // False 
```

The above code serializes the ```State``` object to a standard json:

```json
{"On":true}
```

And deserialization creates a new object typed ```State```, which is not in referential equality with the *initialData*. Still its internal 'state' is restored, the *On* property is set to *true*. This could mean that init-only properties are a C# compiler features only, or the serializers handle init-only properties. Let's see some further examples to decide.

## Reflection

Can I set init-only properties with reflection?

```csharp
var data = new State { On = true };
Console.WriteLine(data.On); // True
typeof(State).GetProperties().First().SetValue(data, false);
Console.WriteLine(data.On); // False
```

Although the design consideration of init-only properties include IL verification, the value of such a property can be modified with reflection even after the object initialization phase.

> Note, we can use the First() Linq extension method on GetProperties safely, because we only have a single property on ```State``` type, and we have full control over this type.

## IL Generation

Can I use IL generator to set such a property? From the example above, we could assume this is possible, but let's prove it through an example:

```csharp
var data = new State { On = true };
Console.WriteLine(data.On); // True
var method = new DynamicMethod("InitOnlySetter", typeof(void), new[] { typeof(State), typeof(bool) });
var generator = method.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Callvirt, typeof(State).GetProperties().First().SetMethod);
generator.Emit(OpCodes.Ret);
var setter = method.CreateDelegate<Action<State, bool>>();
setter(data, false);
Console.WriteLine(data.On); // False
```

The program above is a really simple POC code. It creates a new ```DynamicMethod``` which will create a method with *void* return type and two input arguments typed *State* and *bool*. Then it uses the ```ILGenerator``` to emit IL code to load both load input arguments onto the evaluation stack and then to call the *SetMethod* of the property. Finally it creates an ```Action<State, bool>``` delegate for the emitted IL code, which can be invoked to modify the value of the ```State``` type.

As this is calling the same SetMethod method as before, it should not be a surprise that this can modify the value of an init-only property too.

## Compile C# at Runtime

Lastly, let's see if we compile some C# code from ```string```, can we modify the value? We can compile code dynamically with the help of ```CSharpCompilation``` type.

The code below creates a new string, initialized with the source code to compile. This source code has a *static Set* method in a *static* class called ```Helper```. It parses the string into a syntax tree, then uses the ```CSharpCompilation``` to create a new in memory. For this, two references has to be passed, one to the current dll containing the ```State``` type and one to the System.Private.CoreLib.dll containing the core .NET types. Once the compilation is done and successful, we can load the stream into the current AppDomain (there is a single in .NET 5 we can use), find the respected ```Helper``` type and *Set* method to invoke it.

```csharp
var data = new State { On = true };
string code = @"
public static class Helper
{
    public static void Set(State state, bool value) => state.On = value;
}";

Console.WriteLine(data.On);
var syntaxTree = CSharpSyntaxTree.ParseText(code);
var references = new List<PortableExecutableReference>();
references.Add(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(Path.Combine(Environment.CurrentDirectory, "InitializeRecords.dll")));
var compilation = CSharpCompilation.Create("helper", new[] { syntaxTree }, references,
    new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

using var stream = new MemoryStream();
var result = compilation.Emit(stream);
if (!result.Success)
    throw new Exception(result.Diagnostics.First().ToString());
stream.Seek(0, SeekOrigin.Begin);
var assembly = Assembly.Load(stream.ToArray());
assembly.GetType("Helper").GetMethod("Set").Invoke(null, new object[] { data, false });
Console.WriteLine(data.On);
```

While the above program works perfectly with a regular setter, with init-only properties it returns an error. In the above code an exception is thrown upon checking if the compilation result is successful:

```
'(4,56): error CS8852: Init-only property or indexer 'State.On' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.'
```

In case of the init-only property, the C# compiler knows and enforces the rules for them, so one can be only set during the object initialization phase. As C# code is compiled the compiler will return the error.

## Conclusion

Init-only properties, and respected rules are not enforced by IL Verification by the runtime, at this time it is a C# compiler feature.