This is a super-quick post to hopefully save someone the trouble of figuring out how to detect init-only properties (as I had to this morning).
Init-only properties are a new feature in C# 9, and let you define properties that can only be set in the constructor or in an object initialisation block:
public class MyObject
{
public string InitOnlyProperty { get; init; }
}
static void Main(string[] args)
{
var newInstance = new MyObject
{
InitOnlyProperty = "value"
};
// Compiler error!!
newInstance.InitOnlyProperty = "something";
}
The init;
used instead of set;
marks the property as init-only.
If you want to check if any given property is init-only at runtime, it’s a little more complicated than just checking a single flag on PropertyInfo
.
Looking at the generated IL for our code in dotPeek, we can see that the generated set method’s return value has an additional modreq
option for System.Runtime.CompilerServices.IsExternalInit
.
// .property instance string InitOnlyProperty()
public string InitOnlyProperty
{
// .method public hidebysig specialname instance string
// get_InitOnlyProperty() cil managed
[CompilerGenerated] get
// .maxstack 8
//
// IL_0000: ldarg.0 // this
// IL_0001: ldfld string InitOnlyDetection.Program/MyObject::'<InitOnlyProperty>k__BackingField'
// IL_0006: ret
//
{
return this.\u003CInitOnlyProperty\u003Ek__BackingField;
}
// .method public hidebysig specialname instance void modreq ([System.Runtime]System.Runtime.CompilerServices.IsExternalInit)
// set_InitOnlyProperty(
// string 'value'
// ) cil managed
[CompilerGenerated] set
// .maxstack 8
//
// IL_0000: ldarg.0 // this
// IL_0001: ldarg.1 // 'value'
// IL_0002: stfld string InitOnlyDetection.Program/MyObject::'<InitOnlyProperty>k__BackingField'
// IL_0007: ret
//
{
this.\u003CInitOnlyProperty\u003Ek__BackingField = value;
}
}
So, we can detect init-only properties with the following extension method:
using System.Linq;
using System.Reflection;
public static class PropertyExtensions
{
/// <summary>
/// Determines if this property is marked as init-only.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is init-only, false otherwise.</returns>
public static bool IsInitOnly(this PropertyInfo property)
{
if (!property.CanWrite)
{
return false;
}
var setMethod = property.SetMethod;
// Get the modifiers applied to the return parameter.
var setMethodReturnParameterModifiers = setMethod.ReturnParameter.GetRequiredCustomModifiers();
// Init-only properties are marked with the IsExternalInit type.
return setMethodReturnParameterModifiers.Contains(typeof(System.Runtime.CompilerServices.IsExternalInit));
}
}
There you go, told you it would be super quick!