Previous Page
Next Page

Comparing Fields and Methods

First, let's recap the original motivation for using methods to hide fields.

Consider the following struct that represents a position on a screen as an (X, Y) coordinate pair:

struct ScreenPosition
{
    public ScreenPosition(int x, int y)
    {
        this.X = rangeCheckedX(x);
        this.Y = rangeCheckedY(y);
    }

    public int X;
    public int Y;

    private static int rangeCheckedX(int x)
    {
        if (x < 0 || x > 1280)
        {
            throw new ArgumentOutOfRangeException("X");
        } 
        return x;
    }
    private static int rangeCheckedY(int y)
    {
        if (y < 0 || y > 1024)
        {
            throw new ArgumentOutOfRangeException("Y");
        }
        return y; 
    }
}

The problem with this struct is that it does not follow the golden rule of encapsulation; it does not keep its data private. Public data is a bad idea because its use cannot be checked and controlled. For example, the ScreenPosition constructor range checks its parameters, but no such check can be done on the “raw” access to the public fields. Sooner or later (probably sooner), either X or Y will stray out of its range, possibly as the result of a programming error:

ScreenPosition origin = new ScreenPosition(0, 0);
...
int xpos = origin.X;
origin.Y = -100; // Oops

The common way to solve this problem is to make the fields private and add an accessor method and a modifier method to respectively read and write the value of each private field. The modifier methods can then range-check the new field values because the constructor already checks the initial field values. For example, here's an accessor (GetX) and a modifier (SetX) for the X field. Notice how SetX checks its parameter value:

struct ScreenPosition
{
    ...
    public int GetX()
    {
        return this.x;
    }

    public void SetX(int newX)
    {
       this.x = rangeCheckedX(newX);
    }
    ...
    private static int rangeCheckedX(int x) { ... } 
    private static int rangeCheckedY(int y) { ... }
    private int x, y;
}

The code now successfully enforces the range constraints, which is good. However, there is a price to pay for this valuable guarantee—ScreenPosition no longer has a natural field-like syntax; it uses awkward method-based syntax instead. The following example increases the value of X by 10. To do so, it has to read the value of X by using the GetX accessor method, and then write the value of X by using the SetX modifier method:

int xpos = origin.GetX();
origin.SetX(xpos + 10);

Compare this with the equivalent code if the X field were public:

origin.X += 10;

There is no doubt that, in this case, using fields is cleaner, shorter, and easier. Unfortunately, using fields breaks encapsulation. Properties allow you to combine the best of both examples: to retain encapsulation while allowing a field-like syntax.


Previous Page
Next Page