ReadOnlyBitFieldAttribute

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.

📖 Next: ReadOnlyExtendedBitField →