Encapsulating the Data
In addition to properties and the access modifiers we looked at earlier in the chapter, there are several other specialized ways of encapsulating the data within a class. For instance, there are two more field modifiers. The first is the const modifier, which you already encountered when declaring local variables. The second is the capability of fields to be defined as read-only.
const
Just as with const values, a const field contains a compile-time–determined value that cannot be changed at runtime. Values such as pi make good candidates for constant field declarations. Listing 5.42 shows an example of declaring a const field.
LISTING 5.42: Declaring a Constant Field
class ConvertUnits
{
public const float CentimetersPerInch = 2.54F;
public const int CupsPerGallon = 16;
// ...
}
Constant fields are static automatically, since no new field instance is required for each object instance. Declaring a constant field as static explicitly will cause a compile error. Also, constant fields are usually declared only for types that have literal values (string, int, and double, for example). Types such as Program or System.Guid cannot be used for constant fields.
It is important that the types of values used in public constant expressions are permanent in time. Values such as pi, Avogadro’s number, and the circumference of the Earth are good examples. However, values that could potentially change over time are not. Build numbers, population counts, and exchange rates would be poor choices for constants.
readonly
Unlike const, the readonly modifier is available only for fields (not for local variables). It declares that the field value is modifiable only from inside the constructor or via an initializer during declaration. Listing 5.43 demonstrates how to declare a read-only field.
LISTING 5.43: Declaring a Field As readonly
class Employee
{
public Employee(int id)
{
_Id = id;
}
// ...
public readonly int _Id;
public int Id
{
get { return _Id; }
}
// Error: A readonly field cannot be assigned to (except
// in a constructor or a variable initializer)
// public void SetId(int id) =>
// _Id = id;
// ...
}
Unlike constant fields, readonly-decorated fields can vary from one instance to the next. In fact, a read-only field’s value can change within the constructor. Furthermore, read-only fields occur as either instance or static fields. Another key distinction is that you can assign the value of a read-only field at execution time rather than just at compile time. Given that read-only fields must be set in the constructor or initializer, such fields are the one case where the compiler requires the fields be accessed from code outside their corresponding property. Besides this one exception, you should avoid accessing a backing field from anywhere other than its wrapping property.
Another important feature of readonly-decorated fields over const fields is that read-only fields are not limited to types with literal values. It is possible, for example, to declare a readonly System.Guid instance field:
public static readonly Guid ComIUnknownGuid =
new Guid("00000000-0000-0000-C000-000000000046");
The same, however, is not possible using a constant because of the fact that there is no C# literal representation of a Guid.
Begin 6.0
Given the guideline that fields should not be accessed from outside their wrapping property, those programming in a C# 6.0 world will discover that that there is almost never a need to use the readonly modifier. Instead, it is preferable to use a read-only automatically implemented property, as discussed earlier in the chapter.
Consider Listing 5.44 for one more read-only example.
LISTING 5.44: Declaring a Read-Only Automatically Implemented Property
class TicTacToeBoard
{
// Set both player's move to all false (blank).
// | |
// ---+---+---
// | |
// ---+---+---
// | |
public bool[,,] Cells { get; } = new bool[2, 3, 3];
// Error: The property Cells cannot
// be assigned to because it is read-only
public void SetCells(bool[,,] value) =>
Cells = new bool[2, 3, 3];
// ...
}
Whether implemented using C# 6.0 read-only automatically implemented properties or the readonly modifier on a field, providing for immutability of the array reference is a useful defensive coding technique. It ensures that the array instance remains the same, while allowing the elements within the array to change. Without the read-only constraint, it would be all too easy to mistakenly assign a new array to the member, thereby discarding the existing array rather than updating individual array elements. In other words, using a read-only approach with an array does not freeze the contents of the array. Rather, it freezes the array instance (and therefore the number of elements in the array) because it is not possible to reassign the value to a new instance. The elements of the array are still writeable.
End 6.0