Friday, May 8, 2015

Encapsulating property state in C#

Introduction

In computer programming the state of an object is stored in data fields. This allowed callers to view and modify the state of the object at will.

This had two drawbacks:

  1. Since the fields were uncontrolled, we had to validate the data every time we needed to use it. (This wasn’t essential for private fields, but data corruption was possible.)
  2. We cannot know when fields are updated.

To overcome the above problems, people used 'get' and 'set' methods and hide the underlying field. This was cumbersome and unnatural. As a result, C# introduced properties.

C# Properties - syntactic sugar

C# properties encapsulated the state of an object, as seen by external callers. The external caller could now reference the state in a more natural way, while still ensuring a consistent state.

The advantage of properties is that IDEs such as Visual Studio can do reference counting. Also, they are necessary when using the MVVM model with WPF programs.

private string _MyProperty;

public string MyPropery
{
    get
    {
        return _MyProperty;
    }

    set
    {
        // Add validation code here
        _MyProperty = value;
    }
}
Unfortunately, with this we have four problems:

  1. Broken encapsulation. We can easily bypass validation and break encapsulation by using _MyProperty directly within the class.
  2. Infinite recursion. If you're not careful and mix using _MyProperty and MyProperty, you might end up with one calling the other endlessly.
  3. Refactoring. Since we have two items referring to the same logical state, added complexity is introduced when the time to refactor the code comes.
  4. The maintenance costs increase as the number of properties increase. This can be seen in WPF programs that use the MVVM model. It can be a pain maintaining 30 public properties, referencing 30 private fields that do nothing but store state.

Therefore, C# properties don't encapsulate state in any object oriented way.
There is an exception to this.

public string MyProperty { get; set; }

public string MyReadOnlyProperty { get; private set; }

With this construct, the compiler creates a hidden field that holds the state. Unfortunately, this is little more than a field with a few added benefits.

C# Properties - true encapsulation, true OOP

To enable true OOP properties, we need to be able to encapsulate state. I would like to propose two possible ways of encapsulating state.

Dedicated keyword $state

We can introduce a keyword to represent state. The compiler creates a hidden private class-level field to store the state.

Note: This is not a replacement for private fields.

public string MyProperty
{
    get
    {
        return $state;
    }

    set
    {
        if (!$state.Equals(value))
        {
            // Validation code
            // Kickoff relevant events
            // Assign $state as necessary, or throw an exception
        }
    }
}

The keyword $state is only valid within the body of a get or set.

Used within the class, it would look like this:

public class MyClass
{
    public string MyProperty
    {
        get
        {
            return $state;
        }
    
        set
        {
            if (!$state.Equals(value))
            {
                // Validation code
                // Kickoff relevant events
                // Assign $state as necessary, or throw an exception
            }
        }
    }
    
    // Constructor
    public MyClass(string newProperty)
    {
        MyProperty = newProperty;
    }
    
    public string SomeMethod1()
    {
        // Syntax error. $state means nothing within this context.
        return $state;
    }
    
    public string SomeMethod2()
    {
        return MyProperty;
    }
}

Turn a property into a pseudo class
I include this as something for people to think about. What if we treat properties as pseudo classes?

public string MyProperty
{
    String myState;

    // Initialize state of property at object creation time.
    MyProperty(string state)
    {
        myState = state;
    }

    get
    {
        return state;
    }

    set
    {
        if (!state.Equals(value))
        {
            // Validation code
            // Kickoff relevant events
            // Assign $state as necessary, or throw an exception
        }
    }
}