Class Invariants and Use of Object Initializers

If you are writing object oriented code and you try to ensure that your classes maintain their invariants (i.e., ensure that your classes are always in a valid and self-consistent state), the new object initializer feature of C# will present you with some new challenges.

As an example of the difficulties that can be encountered (and sidestepping the discussion of whether a triangle should be an immutable object), let’s look at a triangle class that takes two angles and determines the other angle (the sum of the angles must equal 180):

        public class Triangle
        {
            private int angle1;
            private int angle2;

            public Triangle(int angle1, int angle2)
            {
                Angle1 = angle1;
                Angle2 = angle2;
                
                if ((Angle1 + Angle2 < 180) == false)
                    throw new ArgumentOutOfRangeException("Sum of Angle1 and Angle2 must be less than 180.");
                
                VerifyThatInvariantIsPreserved();
            }

            public int Angle1
            {
                get { return angle1; }
                
                private set
                {
                    if (value <= 0)
                        throw new ArgumentOutOfRangeException("Angle1 must be greater than zero.");
                    
                    angle1 = value;
                }
            }

            public int Angle2
            {
                get { return angle2; }
                
                private set
                {
                    if (value <= 0)
                        throw new ArgumentOutOfRangeException("Angle2 must be greater than zero.");
                    
                    angle2 = value;
                }
            }

            public int Angle3
            {
                get { return 180 - (Angle1 + Angle2); }
            }

            private void VerifyThatInvariantIsPreserved()
            {
                if (Angle1 + Angle2 + Angle3 != 180)
                    throw new InvariantException("Sum of angles must equal 180.");
                
                if ((Angle1 > 0 && Angle2 > 0 && Angle3 > 0) == false)
                    throw new InvariantException("All angles of the triangle must be positive.");
            }
        }

This class manages the angles of a triangle.  The angle values are assumed to be integers.  The class invariant is that the sum of the angles must be 180 and none of the angles can be 0 or less.  Remember that the class invariant must be true after the constructor has executed and after any public member functions have executed (the invariant can be “violated” by private functions used by the public functions – it just has to be restored on exit from the public function).  The setters for Angle1 and Angle2 are private and are only executed by the constructor.  After setting the properties the constructor validates a cross property relationship (the sum of Angle1 and Angle2 is less than 180 – so that Angle3 can be positive).  To make verification of the invariant explicit I’ve added a private method (“VerifyThatInvariantIsPreserved”) that does just that.  To avoid the overhead of invariant checking in production code, some will choose to use conditional compilation to remove the calls at run-time; others will choose to use unit tests to validate the invariant.

Now if we want to utilize object initializers to initialize the class, e.g.,

Triangle triangle = new Triangle {Angle1 = 45, Angle2 = 45};

,we have to make some modifications to the class.

First of all we need to create public setters for Angle1 and Angle2 and add a public default constructor.  The default constructor is required because the compiler first creates a default Triangle object and then invokes the public setters to initialize the object.  A first attempt looks like this:

        public class Triangle
        {
            private int angle1;
            private int angle2;

            public Triangle()
            {
                VerifyThatInvariantIsPreserved();
            }

            public Triangle(int angle1, int angle2)
            {
                Angle1 = angle1;
                Angle2 = angle2;
                
                if ((Angle1 + Angle2 < 180) == false) 
                    throw new ArgumentOutOfRangeException("Sum of Angle1 and Angle2 must be less than 180.");
                
                VerifyThatInvariantIsPreserved();
            }

            public int Angle1
            {
                get { return angle1; }
                set
                {
                    if (value <= 0) 
                        throw new ArgumentOutOfRangeException("Angle1 must be greater than zero.");
                    
                    angle1 = value;
                    
                    VerifyThatInvariantIsPreserved();
                }
            }

            public int Angle2
            {
                get { return angle2; }
                set
                {
                    if (value <= 0) 
                        throw new ArgumentOutOfRangeException("Angle2 must be greater than zero.");
                    
                    angle2 = value;
                    
                    VerifyThatInvariantIsPreserved();
                }
            }

            public int Angle3
            {
                get { return 180 - (Angle1 + Angle2); }
            }

            private void VerifyThatInvariantIsPreserved()
            {
                if (Angle1 + Angle2 + Angle3 != 180) 
                    throw new InvariantException("Sum of angles must equal 180.");
                
                if ((Angle1 > 0 && Angle2 > 0 && Angle3 > 0) == false) 
                    throw new InvariantException("All angles of the triangle must be positive.");
            }
        }

While this code compiles, it has some unfortunate problems.

  1. An empty default constructor does not preserve the invariant (all the angles are zero by default) which means that an exception will be thrown when we use the object initializer syntax.

    We could remedy this by having the default constructor create a right triangle or an equilateral triangle, e.g.,

    public Triangle():this(90, 45) 
    { 
    }

    While this could work for the specific case of a triangle, it may be more difficult to come up with meaningful defaults for other classes.  It should be noted that this approach also causes double initialization of the object (first when the right triangle is created, then when the values passed in the object initializer are assigned to the public properties).

  2. The public interface of the object now allows the user to modify Angle1 and Angle2 independently and there are now some orderings of calls that will never work, e.g., 
Triangle triangle = new Triangle { Angle1 = 45, Angle2 = 45 };    
triangle.Angle1 = 140;  // throws InvariantException    
triangle.Angle2 = 30;

This is because the new values must be must be set in a single update, i.e., they must both be set to the new values before the class invariant is verified.   One way out of this dilemma is to change the definition of the class so that the constructor (and a new SetAngles property) take some other class which contained the two angles, like so:

public class AngleAngle
{
    private readonly int angle1;
    private readonly int angle3;

    public AngleAngle(int angle1, int angle3)
    {
        this.angle1 = angle1;
        this.angle3 = angle3;
    }
}

However, if you’re doing OO, this just begs the question, because the AngleAngle class should ensure that it is in a valid state (both angles greater than zero and their sum less than 180) and now you’re back to wanting to use object initializers to create the AngleAngle class.

Observations

If you want to maintain invariants in your objects and,

  1. If your class has an invariant that requires two or more members be related to each other in some way (e.g., their sum must be equal to 180), those members should not be set using object initializers.

  2. If you can’t establish a meaningful default value for those members, then they must be set through the constructor and you can’t use object initializers for your class.

  3. If there is a relationship between two members that must be maintained, any setters must update those members in a single update method.

So, if you don’t have the previous issues should you use object invariants?  You might want to ask yourself a couple more questions:

  1. Is it possible that this class will come to have members that are related to each other as part of the class invariant?  If so, and you make use of object initializers, you will face the maintenance issue of having to go back and remove the object initializers. 
  2. If not, then do your classes exhibit low cohesion (i.e., are they containers unrelated data)?  While there will always be classes that have low cohesion in any given design, a design in which the majority of the classes exhibit low cohesion is a potential code smell of its own.

Conclusions

If your object oriented design style is such that you like to write classes that ensure that their own invariants are maintained, you will want to avoid using object initializers except for classes that you are sure will never have invariants that involve relationships between multiple members.