ReadOnlyBitFieldAttribute extends the functionality of BitFieldAttribute. It provides the same bit-packing features while also generating constructors for initializing read-only bit-packed properties.
What is ReadOnlyBitFieldAttribute?​
✅ Inherits from BitFieldAttribute, meaning it supports all standard bit-packing features
✅ Generates constructors automatically for read-only properties
✅ Requires either a read-only getter or an init accessor
✅ Supports FieldName to reference an existing field, but it must be readonly
✅ Groups constructors based on ConstructorAccessModifier, allowing multiple constructors
✅ By default, the constructor is private unless explicitly changed using ConstructorAccessModifier
Example: Read-Only Bit-Packed Properties​
public sealed partial class ReadOnlyBitFieldExample
{
private readonly byte _existing;
[ReadOnlyBitField] // Default constructor is private
public partial bool Flag1 { get; }
[ReadOnlyBitField(ConstructorAccessModifier = AccessModifier.Public)]
public partial bool Flag2 { get; }
[ReadOnlyBitField(ConstructorAccessModifier = AccessModifier.Public)]
public partial bool Flag3 { get; }
[ReadOnlyBitField(ConstructorAccessModifier = AccessModifier.Public)]
public partial int AdditionalData { get; }
[ReadOnlyBitField(BitsCount = 15, FieldName = "_bitField", ConstructorAccessModifier = AccessModifier.Public)]
public partial int AdditionalData2 { get; }
[ReadOnlyBitField(BitsCount = 1, FieldName = "_bitField", ConstructorAccessModifier = AccessModifier.Public)]
public partial int AdditionalData3 { get; }
[ReadOnlyBitField(BitsCount = 4, FieldName = nameof(_existing), ConstructorAccessModifier = AccessModifier.Public)]
public partial byte AdditionalData4 { get; }
}
Constructor Generation Based on ConstructorAccessModifier​
The attribute groups properties by access modifier when generating constructors.
Default (Private) Constructor​
If no ConstructorAccessModifier is specified, the constructor is private.
partial class ReadOnlyBitFieldExample
{
private ReadOnlyBitFieldExample(bool Flag1)
{
{
this._Flag1__Flag2__Flag3__AdditionalData__ = (ulong)(Flag1 ? ((this._Flag1__Flag2__Flag3__AdditionalData__) | (((1UL << 1) - 1) << 0)) : (this._Flag1__Flag2__Flag3__AdditionalData__ & ~(((1UL << 1) - 1) << 0)));
}
}
}
Public Constructor for Properties with Public Access​
Properties with the same ConstructorAccessModifier are grouped into the same constructor.
partial class ReadOnlyBitFieldExample
{
public ReadOnlyBitFieldExample(sbyte AdditionalData4, bool Flag2, bool Flag3, int AdditionalData, int AdditionalData2, int AdditionalData3)
{
{
const byte maxAdditionalData4_ = (1 << 4) - 1;
var clampedAdditionalData4_ = global::System.Math.Min((byte)(AdditionalData4), maxAdditionalData4_);
this._existing = (byte)((this._existing & ~(((1 << 4) - 1) << 0)) | ((clampedAdditionalData4_ & ((1 << 4) - 1)) << 0));
}
{
this._Flag1__Flag2__Flag3__AdditionalData__ = (ulong)(Flag2 ? ((this._Flag1__Flag2__Flag3__AdditionalData__) | (((1UL << 1) - 1) << 1)) : (this._Flag1__Flag2__Flag3__AdditionalData__ & ~(((1UL << 1) - 1) << 1)));
}
{
this._Flag1__Flag2__Flag3__AdditionalData__ = (ulong)(Flag3 ? ((this._Flag1__Flag2__Flag3__AdditionalData__) | (((1UL << 1) - 1) << 2)) : (this._Flag1__Flag2__Flag3__AdditionalData__ & ~(((1UL << 1) - 1) << 2)));
}
{
const ulong maxAdditionalData_ = (1UL << 32) - 1;
var clampedAdditionalData_ = global::System.Math.Min((ulong)(AdditionalData), maxAdditionalData_);
this._Flag1__Flag2__Flag3__AdditionalData__ = (ulong)((this._Flag1__Flag2__Flag3__AdditionalData__ & ~(((1UL << 32) - 1) << 3)) | ((clampedAdditionalData_ & ((1UL << 32) - 1)) << 3));
}
{
const ushort maxAdditionalData2_ = (1 << 15) - 1;
var clampedAdditionalData2_ = global::System.Math.Min((ushort)(AdditionalData2), maxAdditionalData2_);
this._bitField = (ushort)((this._bitField & ~(((1 << 15) - 1) << 0)) | ((clampedAdditionalData2_ & ((1 << 15) - 1)) << 0));
}
{
const ushort maxAdditionalData3_ = (1 << 1) - 1;
var clampedAdditionalData3_ = global::System.Math.Min((ushort)(AdditionalData3), maxAdditionalData3_);
this._bitField = (ushort)((this._bitField & ~(((1 << 1) - 1) << 15)) | ((clampedAdditionalData3_ & ((1 << 1) - 1)) << 15));
}
}
}
Common Errors​
1️⃣ Referencing a Non-Readonly Field​
If FieldName references a non-readonly field, the generator throws an error:
PRBITS013
Invalid reference to non-readonly field in 'ReadOnlyBitFieldAttribute.FieldName'.
The 'FieldName' for property {0} must reference a readonly field when using the nameof operation.
✅ Fix: Ensure the field is declared as readonly.
private readonly int _bitField; // ✅ Allowed
[ReadOnlyBitField(FieldName = nameof(_bitField))]
public partial int Data { get; }
🚫 Invalid Code (Causes Error)
private int _bitField; // ❌ Not readonly
[ReadOnlyBitField(FieldName = nameof(_bitField))]
public partial int Data { get; } // ❌ Compiler error: PRBITS013
2️⃣ Property Must Be Read-Only or Have init​
If a property uses a setter, it must be init-only. Otherwise, an error occurs:
PRBITS014
ReadOnlyBitFieldAttribute requires property without setter or with init-only setter.
The property {0} with ReadOnlyBitFieldAttribute must either be read-only or have an init-only setter.
🚫 Invalid Code (Causes Error)
[ReadOnlyBitField]
public partial int Value { get; set; } // ❌ Compiler error: PRBITS014
✅ Valid Code
[ReadOnlyBitField]
public partial int Value { get; } // ✅ Read-only property
[ReadOnlyBitField]
public partial int Value2 { get; init; } // ✅ Init-only setter
Why Use ReadOnlyBitFieldAttribute?​
✅ Ensures immutability – Values are set only through the constructor
✅ Optimized for performance – Memory-efficient bit-packing with zero runtime overhead
✅ Supports field customization – Use FieldName to pack properties into an existing field
✅ Automatically generates constructors – Properties with the same ConstructorAccessModifier are grouped
✅ Defaults to private constructors unless explicitly specified
Important Notes​
⚠ ReadOnlyBitFieldAttribute inherits from BitFieldAttribute, so it supports all the same features.
⚠ The generator automatically creates constructors based on ConstructorAccessModifier.
⚠ If a property exceeds the allocated bit size, compilation will fail with an error.
⚠ By default, the constructor is private. Use ConstructorAccessModifier to change visibility.