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

 
 
  • Gérald Barré

C#9 introduces the concept of init-only properties. These properties can be set during object initialization and become read-only once the object is fully created.

C#
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.
}

You might assume init-only properties are read-only once the object is created. However, when using reflection, init-only properties look like any other property with a setter, as if they were declared { get; set; }.

C#
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:

C#
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 whether a property is init-only by checking its required custom modifiers. By looking at the IL of the init-only properties, you can see the modreq with the IsExternalInit type:

C#
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);
}
C#
PropertyInfo propertyInfo = typeof(Person).GetProperty(nameof(Person.Username));
var isInitOnly = propertyInfo.IsInitOnly();

#Conclusion

Init-only properties are read-only at the language level once an object is fully created, but this is only a compiler constraint. The generated code still contains a setter, which can be invoked at runtime to modify the property value. If you use reflection to edit an object and want to skip init-only properties, check whether the Required Custom Modifiers (modreq) contain the IsExternalInit type.

#Additional resources

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

Follow me:
Enjoy this blog?