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!