C#9 - init-only properties are not read-only at runtime

  • Gérald Barré

C#9 adds the concept of init-only properties. These properties can be set when creating the object and become get only once object creation has been completed.

class Person
{
    public string Name { get; init; }
    public string Username { get; init; }
}

void A()
{
    var author = new Person
    {
        Name = "Gérald Barré",  // ok
        Username = "meziantou", // ok
    };

    author.Username = "edited"; // Error CS8852 Init-only property or indexer 'Person.Username' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
}

So, you may think the init-only properties can be considered read-only once the object is created. However, when using reflection, init-only properties look like any other property. They have a setter that you can call it as if the property were a { get; set; }.

PropertyInfo propertyInfo = typeof(Person).GetProperty(nameof(Person.Username));
propertyInfo.SetValue(author, "edited");
Console.WriteLine(author.Username); // Print "edited"

The same applies to records as the compiler generates init-only properties for positional record members:

record Person(string Name, string Username);

void A()
{
    var author = new Person("Gérald Barré", "meziantou");

    PropertyInfo propertyInfo = typeof(Person).GetProperty(nameof(Person.Username));
    propertyInfo.SetValue(author, "edited");
    Console.WriteLine(author.Username); // Print "edited"
}

#Detecting init-only properties with reflection

When using reflection, you can detect that a property is an init-only reflection by checking the required custom modifiers. By looking at the IL of the init-only properties, you can see the modreq with the IsExternalInit type:

public static bool IsInitOnly(this PropertyInfo propertyInfo)
{
    MethodInfo setMethod = propertyInfo.SetMethod;
    if (setMethod == null)
        return false;

    var isExternalInitType = typeof(System.Runtime.CompilerServices.IsExternalInit);
    return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(isExternalInitType);
}
PropertyInfo propertyInfo = typeof(Person).GetProperty(nameof(Person.Username));
var isInitOnly = propertyInfo.IsInitOnly();

#Conclusion

Init-only properties are read-only at the language level once the object is fully created. But this is just a compiler constraint. The generated code contains a setter, so you can use it at runtime to edit the value of the property. If you are using reflection to edit an object and want to skip init-only properties, you need to check if the Required Custom Modifiers (modreq) contains the IsExternalInit type.

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee