Search code examples
c#compiler-errorspropertiesaccess-modifiersc#-9.0

How to propagate the value of an init-only property to an enclosed class?


I have a Wheel class with two init-only properties Spokes and DiskBrake:

public class Wheel
{
    internal Wheel() { }

    public int Spokes { get; init; }
    public bool DiskBrake { get; init; }
}

It is critical for the functionality of this class that these properties are immutable after the instantiation of a Wheel object. So it is highly desirable for the properties to be init-only, and backed by a readonly fields.

Now I have a Bicycle class that contains two instances of the Wheel class:

public class Bicycle
{
    private readonly Wheel _frontWheel = new();
    private readonly Wheel _rearWheel = new();

    public int FrontWheelSpokes
    {
        get => _frontWheel.Spokes;
        init => _frontWheel.Spokes = value;
    }

    public int RearWheelSpokes
    {
        get => _rearWheel.Spokes;
        init => _rearWheel.Spokes = value;
    }

    public bool FrontWheelDiskBrake
    {
        get => _frontWheel.DiskBrake;
        init => _frontWheel.DiskBrake = value;
    }

    public bool RearWheelDiskBrake
    {
        get => _rearWheel.DiskBrake;
        init => _rearWheel.DiskBrake = value;
    }
}

The intended usage of this class is:

Bicycle bicycle = new()
{
    FrontWheelSpokes = 32,
    RearWheelSpokes = 24,
    FrontWheelDiskBrake = true,
    RearWheelDiskBrake = false
};

Unfortunately this code doesn't compile. I get four compile errors in the init => _xxxWheel.Yyy = value; lines:

Init-only property or indexer 'Wheel.Spokes' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

Online demo.

Is anything that I can do to fix the compile errors, without compromising the "init-only ness" of all properties in both the Wheel and Bicycle classes?

Constraints:

  • I don't want to expose the constructor of the Wheel class to the outside world. Only the Bicycle itself should instantiate its wheels.
  • Replacing the init-only properties with constructor parameters is highly undesirable.
  • The shape of the Bicycle public API is optimal as is. I don't want to change it.

Solution

  • It is possible to recreate the instance of your wheel property in every init-setter. You have to copy the properties that you do not set. This has the strong disadvantage that you have to call a lot of unnecessary Wheel constructors, but you can keep your class design. The code might be (for brevity, only with front wheel):

    class Bicycle
        {
            private readonly Wheel _frontWheel = new();
    
            public int FrontWheelSpokes
            {
                get => _frontWheel.Spokes;
                init => _frontWheel = new Wheel
                {
                    Spokes = value,
                    DiskBrake = _frontWheel.DiskBrake
                };
            }
    
            public bool FrontWheelDiskBrake
            {
                get => _frontWheel.DiskBrake;
                init => _frontWheel = new Wheel
                {
                    Spokes = _frontWheel.Spokes,
                    DiskBrake = value
                };
            }
    }
    

    Online-demo: https://dotnetfiddle.net/LzMdP5