C#9 - init-only properties are not read-only at runtime
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!