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!