Constructors

Now that you have added fields to a class and can store data, you need to consider the validity of that data. As you saw in Listing 5.3, it is possible to instantiate an object using the new operator. The result, however, is the ability to create an employee with invalid data. Immediately following the assignment of employee, you have an Employee object whose name and salary are not initialized. In this particular listing, you assigned the uninitialized fields immediately following the instantiation of an employee, but if you failed to do the initialization, you would not receive a warning from the compiler. As a result, you could end up with an Employee object with an invalid name.

Declaring a Constructor

To correct this problem, you need to provide a means of specifying the required data when the object is created. You do this using a constructor as demonstrated in Listing 5.25.

LISTING 5.25: Defining a Constructor


class Employee
{
  // Employee constructor                                                   
  public Employee(string firstName, string lastName)                        
  {                                                                         
    FirstName = firstName;                                                  
    LastName = lastName;                                                    
  }                                                                         

  public string FirstName{ get; set; }
  public string LastName{ get; set; }
  public string Salary{ get; set; } = "Not Enough";

  // ...
}


As shown here, to define a constructor you create a method with no return type, whose method name is identical to the class name.

The constructor is the method that the runtime calls to initialize an instance of the object. In this case, the constructor takes the first name and the last name as parameters, allowing the programmer to specify these names when instantiating the Employee object. Listing 5.26 is an example of how to call a constructor.

LISTING 5.26: Calling a Constructor


class Program
{
  static void Main()
  {
      Employee employee;
      employee = new Employee("Inigo", "Montoya");                            
      employee.Salary = "Too Little";

      System.Console.WriteLine(
          "{0} {1}: {2}",
          employee.FirstName,
          employee.LastName,
          employee.Salary);
  }
  // ...
}


Notice that the new operator returns the type of the object being instantiated (even though no return type or return statement was specified explicitly in the constructor’s declaration or implementation). In addition, you have removed the initialization code for the first and last names because that initialization takes place within the constructor. In this example, you don’t initialize Salary within the constructor, so the code assigning the salary still appears.

Developers should take care when using both assignment at declaration time and assignment within constructors. Assignments within the constructor will occur after any assignments are made when a field is declared (such as string Salary = "Not enough" in Listing 5.5). Therefore, assignment within a constructor will override any value assigned at declaration time. This subtlety can lead to a misinterpretation of the code by a casual reader who assumes the value after instantiation is the one assigned in the field declaration. Therefore, it is worth considering a coding style that does not mix both declaration assignment and constructor assignment within the same class.

Default Constructors

When you add a constructor explicitly, you can no longer instantiate an Employee from within Main() without specifying the first and last names. The code shown in Listing 5.27, therefore, will not compile.

LISTING 5.27: Default Constructor No Longer Available


class Program
{
  static void Main()
  {
      Employee employee;
      // ERROR: No overload because method 'Employee'                        
      // takes '0' arguments.                                                
      employee = new Employee();                                             

      // ...
  }
}


If a class has no explicitly defined constructor, the C# compiler adds one during compilation. This constructor takes no parameters and, therefore, is the default constructor by definition. As soon as you add an explicit constructor to a class, the C# compiler no longer provides a default constructor. Therefore, with Employee(string firstName, string lastName) defined, the default constructor, Employee(), is not added by the compiler. You could manually add such a constructor, but then you would again be allowing construction of an Employee without specifying the employee name.

It is not necessary to rely on the default constructor defined by the compiler. It is also possible for programmers to define a default constructor explicitly—perhaps one that initializes some fields to particular values. Defining the default constructor simply involves declaring a constructor that takes no parameters.

Object Initializers

Begin 3.0

Starting with C# 3.0, the C# language team added functionality to initialize an object’s accessible fields and properties using an object initializer. The object initializer consists of a set of member initializers enclosed in curly braces following the constructor call to create the object. Each member initializer is the assignment of an accessible field or property name with a value (see Listing 5.28).

LISTING 5.28: Calling an Object Initializer


class Program
{
  static void Main()
  {
        Employee employee1 = new Employee("Inigo", "Montoya")
            { Title = "Computer Nerd", Salary = "Not enough"};
      // ...
  }
}


Notice that the same constructor rules apply even when using an object initializer. In fact, the resultant CIL is exactly the same as it would be if the fields or properties were assigned within separate statements immediately following the constructor call. The order of member initializers in C# provides the sequence for property and field assignment in the statements following the constructor call within CIL.

In general, all properties should be initialized to reasonable default values by the time the constructor exits. Moreover, by using validation logic on the setter, it is possible to restrict the assignment of invalid data to a property. On occasion, the values on one or more properties may cause other properties on the same object to contain invalid values. When this occurs, exceptions from the invalid state should be postponed until the invalid interrelated property values become relevant.


Guidelines

DO provide sensible defaults for all properties, ensuring that defaults do not result in a security hole or significantly inefficient code. For automatically implemented properties, set the default via the constructor.

DO allow properties to be set in any order, even if this results in a temporarily invalid object state.


End 3.0

Overloading Constructors

Constructors can be overloaded—you can have more than one constructor as long as the number or types of the parameters vary. For example, as Listing 5.30 shows, you could provide a constructor that has an employee ID with first and last names, or even just the employee ID.

LISTING 5.30: Overloading a Constructor


class Employee
{
  public Employee(string firstName, string lastName)
  {
      FirstName = firstName;
      LastName = lastName;
  }

  public Employee(                                                                 
      int id, string firstName, string lastName )                                  
  {                                                                                
      Id = id;                                                                     
      FirstName = firstName;                                                       
      LastName = lastName;                                                         
  }                                                                                
                                                                                   
  public Employee(int id)                                                          
  {                                                                                
      Id = id;                                                                     
                                                                                   
      // Look up employee name...                                                  
      // ...                                                                       
  }                                                                                
                                                                                   
  public int Id { get; set; }                                                      
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string Salary { get; set; } = "Not Enough";

  // ...
}


This approach enables Program.Main() to instantiate an employee from the first and last names either by passing in the employee ID only or by passing both the names and the IDs. You would use the constructor with both the names and the IDs when creating a new employee in the system. You would use the constructor with only the ID to load up the employee from a file or a database.

As is the case with method overloading, multiple constructors are used to support simple scenarios using a small number of parameters and complex scenarios with additional parameters. Consider using optional parameters in favor of overloading so that the default values for “defaulted” properties are visible in the API. For example, a constructor signature of Person(string firstName, string lastName, int? age = null) provides signature documentation that if the Age of a Person is not specified, it will default to null.


Guidelines

DO use the same name for constructor parameters (camelCase) and properties (PascalCase) if the constructor parameters are used to simply set the property.

DO provide constructor optional parameters and/or convenience constructor overloads that initialize properties with good defaults.

DO allow properties to be set in any order, even if this results in a temporarily invalid object state.


Constructor Chaining: Calling Another Constructor Using this

Notice in Listing 5.30 that the initialization code for the Employee object is now duplicated in multiple places and, therefore, has to be maintained in multiple places. The amount of code is small, but there are ways to eliminate the duplication by calling one constructor from another—constructor chaining—using constructor initializers. Constructor initializers determine which constructor to call before executing the implementation of the current constructor (see Listing 5.31).

LISTING 5.31: Calling One Constructor from Another


class Employee
{
  public Employee(string firstName, string lastName)
  {
      FirstName = firstName;
      LastName = lastName;
  }

  public Employee(                                                    
      int id, string firstName, string lastName )                     
      : this(firstName, lastName)                                     
  {
      Id = id;
  }

  public Employee(int id)
  {
      Id = id;

      // Look up employee name...
      // ...

      // NOTE:  Member constructors cannot be                         
      // called explicitly inline                                     
      // this(id, firstName, lastName);                               
  }

  public int Id { get; private set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string Salary { get; set; } = "Not Enough";

  // ...
}


To call one constructor from another within the same class (for the same object instance), C# uses a colon followed by the this keyword, followed by the parameter list on the callee constructor’s declaration. In this case, the constructor that takes all three parameters calls the constructor that takes two parameters. Often, this calling pattern is reversed—that is, the constructor with the fewest parameters calls the constructor with the most parameters, passing defaults for the parameters that are not known.

Begin 3.0

End 3.0

Static Members

The HelloWorld example in Chapter 1 briefly touched on the keyword static. This section defines the static keyword more fully.

Let’s consider an example. Assume that the employee Id value needs to be unique for each employee. One way to accomplish this is to store a counter to track each employee ID. If the value is stored as an instance field, however, every time you instantiate an object, a new NextId field will be created such that every instance of the Employee object will consume memory for that field. The biggest problem is that each time an Employee object is instantiated, the NextId value on all of the previously instantiated Employee objects needs to be updated with the next ID value. In this case, what you need is a single field that all Employee object instances share.


Language Contrast: C++/Visual Basic—Global Variables and Functions

Unlike many of the languages that came before it, C# does not have global variables or global functions. All fields and methods in C# appear within the context of a class. The equivalent of a global field or function within the realm of C# is a static field or function. There is no functional difference between global variables/functions and C# static fields/methods, except that static fields/methods can include access modifiers, such as private, that can limit the access and provide better encapsulation.


Static Fields

To define data that is available across multiple instances, you use the static keyword, as demonstrated in Listing 5.34.

LISTING 5.34: Declaring a Static Field


class Employee
{
  public Employee(string firstName, string lastName)
  {
      FirstName = firstName;
      LastName = lastName;
      Id = NextId;                                                          
      NextId++;                                                             
  }

  // ...

  public static int NextId;                                                 
  public int Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string Salary { get; set; } = "Not Enough";

  // ...
}


In this example, the NextId field declaration includes the static modifier and, therefore, is called a static field. Unlike Id, a single storage location for NextId is shared across all instances of Employee. Inside the Employee constructor, you assign the new Employee object’s Id the value of NextId immediately before incrementing the Id. When another Employee class is created, NextId will be incremented and the new Employee object’s Id field will hold a different value.

Just as instance fields (nonstatic fields) can be initialized at declaration time, so can static fields, as demonstrated in Listing 5.35.

LISTING 5.35: Assigning a Static Field at Declaration


class Employee
{
  // ...
  public static int NextId = 42;                                            
  // ...
}


Unlike with instance fields, if no initialization for a static field is provided, the static field will automatically be assigned its default value (0, null, false, and so on)—the equivalent of default(T), where T is the name of the type. As a result, it will be possible to access the static field even if it has never been explicitly assigned in the C# code.

Nonstatic fields, or instance fields, provide a new storage location for each object to which they belong. In contrast, static fields don’t belong to the instance, but rather to the class itself. As a result, you access a static field from outside a class via the class name. Consider the new Program class shown in Listing 5.36 (using the Employee class from Listing 5.34).

LISTING 5.36: Accessing a Static Field


using System;

class Program
{
  static void Main()
  {
      Employee.NextId = 1000000;                                               

      Employee employee1 = new Employee(
          "Inigo", "Montoya");
      Employee employee2 = new Employee(
          "Princess", "Buttercup");

      Console.WriteLine(
          "{0} {1} ({2})",
          employee1.FirstName,
          employee1.LastName,
          employee1.Id);
      Console.WriteLine(
          "{0} {1} ({2})",
          employee2.FirstName,
          employee2.LastName,
          employee2.Id);

      Console.WriteLine(                                                       
          $"NextId = { Employee.NextId }");                                    
  }

  // ...
}


Output 5.9 shows the results of Listing 5.36.

OUTPUT 5.9

Inigo Montoya (1000000)
Princess Buttercup (1000001)
NextId = 1000002

To set and retrieve the initial value of the NextId static field, you use the class name, Employee, rather than a reference to an instance of the type. The only place you can omit the class name is within the class itself (or a derived class). In other words, the Employee(...) constructor did not need to use Employee.NextId because the code appeared within the context of the Employee class itself and, therefore, the context was already understood. The scope of a variable is the program text in which the variable can be referred to by its unqualified name; the scope of a static field is the text of the class (and any derived classes).

Even though you refer to static fields slightly differently than you refer to instance fields, it is not possible to define a static field and an instance field with the same name in the same class. The possibility of mistakenly referring to the wrong field is high, so the C# designers decided to prevent such code. Overlap in names, therefore, introduces conflict within the declaration space.

Static Methods

Just like static fields, you access static methods directly off the class name—for example, as Console.ReadLine(). Furthermore, it is not necessary to have an instance to access the method.

Listing 5.37 provides another example of both declaring and calling a static method.

LISTING 5.37: Defining a Static Method on DirectoryInfo


public static class DirectoryInfoExtension
{
  public static void CopyTo(                                                 
      DirectoryInfo sourceDirectory, string target,                          
      SearchOption option, string searchPattern)                             
  {
      if (target[target.Length - 1] !=
            Path.DirectorySeparatorChar)
      {
          target += Path.DirectorySeparatorChar;
      }
      if (!Directory.Exists(target))
      {
          Directory.CreateDirectory(target);
      }

      for (int i = 0; i < searchPattern.Length; i++)
      {
          foreach (string file in
              Directory.GetFiles(
                  sourceDirectory.FullName, searchPattern))
          {
              File.Copy(file,
                  target + Path.GetFileName(file), true);
          }
      }

      //Copy subdirectories (recursively)
      if (option == SearchOption.AllDirectories)
      {
          foreach(string element in
              Directory.GetDirectories(
                  sourceDirectory.FullName))
          {
              Copy(element,
                  target + Path.GetFileName(element),
                  searchPattern);
          }
      }
  }
}


      // ...
      DirectoryInfo directory = new DirectoryInfo(".\Source");
      directory.MoveTo(".\Root");
      DirectoryInfoExtension.CopyTo(                                         
          directory, ".\Target",                                            
          SearchOption.AllDirectories, "*");                                 
      // ...


In Listing 5.37, the DirectoryInfoExtension.Copy() method takes a DirectoryInfo object and copies the underlying directory structure to a new location.

Because static methods are not referenced through a particular instance, the this keyword is invalid inside a static method. In addition, it is not possible to access either an instance field or an instance method directly from within a static method without a reference to the particular instance to which the field or method belongs. (Note that Main() is another example of a static method.)

One might have expected this method on the System.IO.Directory class or as an instance method on System.IO.DirectoryInfo. Since neither exists, Listing 5.37 defines such a method on an entirely new class. In the section “Extension Methods” later in this chapter, we show how to make it appear as an instance method on DirectoryInfo.

Static Constructors

In addition to static fields and methods, C# supports static constructors. Static constructors are provided as a means to initialize the class itself, rather than the instances of a class. Such constructors are not called explicitly; instead, the runtime calls static constructors automatically upon first access to the class, whether by calling a regular constructor or by accessing a static method or field on the class. Because the static constructor cannot be called explicitly, no parameters are allowed on static constructors.

You use static constructors to initialize the static data within the class to a particular value, primarily when the initial value involves more complexity than a simple assignment at declaration time. Consider Listing 5.38.

LISTING 5.38: Declaring a Static Constructor


class Employee
{
  static Employee()
  {
      Random randomGenerator = new Random();
      NextId = randomGenerator.Next(101, 999);
  }

  // ...
  public static int NextId = 42;
  // ...
}


Listing 5.38 assigns the initial value of NextId to be a random integer between 100 and 1,000. Because the initial value involves a method call, the NextId initialization code appears within a static constructor and not as part of the declaration.

If assignment of NextId occurs within both the static constructor and the declaration, it is not obvious what the value will be when initialization concludes. The C# compiler generates CIL in which the declaration assignment is moved to be the first statement within the static constructor. Therefore, NextId will contain the value returned by randomGenerator.Next(101, 999) instead of a value assigned during NextId’s declaration. Assignments within the static constructor, therefore, will take precedence over assignments that occur as part of the field declaration, as was the case with instance fields. Note that there is no support for defining a static finalizer.

Be careful not to throw an exception from a static constructor, as this will render the type unusable for the remainder of the application’s lifetime.6

6. Technically, the application domain’s lifetime—the CLR’s virtual equivalent of an operating system process.


Guidelines

CONSIDER initializing static fields inline rather than explicitly using static constructors or declaration assigned values.


Static Properties

Begin 2.0

You also can declare properties as static. For example, Listing 5.39 wraps the data for the next ID into a property.

LISTING 5.39: Declaring a Static Property


class Employee
{
  // ...
  public static int NextId                                                 
  {                                                                        
      get                                                                  
      {                                                                    
          return _NextId;                                                  
      }                                                                    
      private set                                                          
      {                                                                    
          _NextId = value;                                                 
      }                                                                    
  }                                                                        
  public static int _NextId = 42;                                          
  // ...
}


It is almost always better to use a static property rather than a public static field, because public static fields are callable from anywhere, whereas a static property offers at least some level of encapsulation.

Begin 6.0

In C# 6.0, the entire NextId implementation—including an inaccessible backing field—can be simplified down to an automatically implemented property with an initializer:

public static int NextId { get; private set; } = 42;

End 6.0

Static Classes

Some classes do not contain any instance fields. Consider, for example, a Math class that has functions corresponding to the mathematical operations Max() and Min(), as shown in Listing 5.40.

LISTING 5.40: Declaring a Static Class


// Static class introduced in C# 2.0
public static class SimpleMath                                              
{
  // params allows the number of parameters to vary.
  public static int Max(params int[] numbers)
  {
      // Check that there is at least one item in numbers.
      if(numbers.Length == 0)
      {
          throw new ArgumentException(
              "numbers cannot be empty", "numbers");
      }

      int result;
      result = numbers[0];
      foreach (int number in numbers)
      {
          if(number > result)
          {
              result = number;
          }
      }
      return result;
  }

  // params allows the number of parameters to vary.
  public static int Min(params int[] numbers)
  {
      // Check that there is at least one item in numbers.
      if(numbers.Length == 0)
      {
          throw new ArgumentException(
              "numbers cannot be empty", "numbers");
      }

      int result;
      result = numbers[0];
      foreach (int number in numbers)
      {
          if(number < result)
          {
              result = number;
          }
      }
      return result;
  }
}


public class Program
{
  public static void Main(string[] args)
  {
      int[] numbers = new int[args.Length];
      for (int count = 0; count < args.Length; count++)
      {
          numbers[count] = args[count].Length;
      }

      Console.WriteLine(
          $@"Longest argument length = {
              SimpleMath.Max(numbers) }");
      Console.WriteLine(
          $@"Shortest argument length = {
              SimpleMath.Min(numbers) }");
  }
}


This class does not have any instance fields (or methods), so creation of such a class would be pointless. Consequently, the class is decorated with the static keyword. The static keyword on a class provides two facilities. First, it prevents a programmer from writing code that instantiates the SimpleMath class. Second, it prevents the declaration of any instance fields or methods within the class. Because the class cannot be instantiated, instance members would be pointless. The Program class in prior listings is another good candidate for a static class because it too contains only static members.

End 2.0

One more distinguishing characteristic of the static class is that the C# compiler automatically marks it as abstract and sealed within the CIL. This designates the class as inextensible; in other words, no class can be derived from this class or even instantiate it.

Begin 6.0

Begin 3.0

In the previous chapter, we saw that the using static directive can be used with static classes such as SimpleMath. For example, adding a using static SimpleMath; declarative at the top of Listing 5.40 would allow you to invoke Max without the SimpleMath prefix:

      Console.WriteLine(
          $@"Longest argument length = { Max(numbers) }");

End 6.0

Extension Methods

Consider the System.IO.DirectoryInfo class, which is used to manipulate filesystem directories. This class supports functionality to list the files and subdirectories (DirectoryInfo.GetFiles()) as well as the capability to move the directory (DirectoryInfo.Move()). One feature it doesn’t support directly is the copy feature. If you needed such a method, you would have to implement it, as shown earlier in Listing 5.37.

The DirectoryInfoExtension.Copy() method is a standard static method declaration. However, notice that calling this Copy() method is different from calling the DirectoryInfo.Move() method. This is unfortunate. Ideally, we want to add a method to DirectoryInfo so that, given an instance, we could call Copy() as an instance method—directory.Copy().

C# 3.0 simulates the creation of an instance method on a different class via extension methods. To do this, we simply change the signature of our static method so that the first parameter—that is, the data type we are extending—is prefixed with the this keyword (see Listing 5.41).

LISTING 5.41: Static Copy Method for DirectoryInfo


public static class DirectoryInfoExtension
{
  public static void CopyTo(
      this DirectoryInfo sourceDirectory, string target,
      SearchOption option, string searchPattern)
  {
      // ...
  }
}


  // ...
      DirectoryInfo directory = new DirectoryInfo(".\Source");
      directory.CopyTo(".\Target",                                            
          SearchOption.AllDirectories, "*");                                   
  // ...


With this simple addition to C# 3.0, it is now possible to add “instance methods” to any class, including classes that are not within the same assembly. The resultant CIL code, however, is identical to what the compiler creates when calling the extension method as a normal static method.

Extension method requirements are as follows.

The first parameter corresponds to the type that the method extends or on which it operates.

To designate the extension method, prefix the extended type with the this modifier.

To access the method as an extension method, import the extending type’s namespace via a using directive (or place the extending class in the same namespace as the calling code).

If the extension method signature matches a signature already found on the extended type (that is, if CopyTo() already existed on DirectoryInfo), the extension method will never be called except as a normal static method.

Note that specializing a type via inheritance (covered in detail in Chapter 6) is preferable to using an extension method. Extension methods do not provide a clean versioning mechanism, because the addition of a matching signature to the extended type will take precedence over the extension method without warning of the change. The subtlety of this behavior is more pronounced for extended classes whose source code you don’t control. Another minor point is that, although development IDEs support IntelliSense for extension methods, simply reading through the calling code does not make it obvious that a method is an extension method.

In general, you should use extension methods sparingly. Do not, for example, define them on type object. Chapter 6 discusses how to use extension methods in association with an interface. Without such an association, defining extension methods is rare.


Guidelines

AVOID frivolously defining extension methods, especially on types you don’t own.


End 3.0

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.


Guidelines

DO use constant fields for values that will never change.

DO NOT use constant fields for values that will change over time.


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.


Guidelines

DO favor use of read-only automatically implemented properties in C# 6.0 (or later) over defining read-only fields.

DO use public static readonly modified fields for predefined object instances prior to C# 6.0.

AVOID changing a public readonly modified field in pre-C# 6.0 to a read-only automatically implemented property in C# 6.0 (or later) if version API compatibility is required.


End 6.0

Nested Classes

In addition to defining methods and fields within a class, it is possible to define a class within a class. Such classes are called nested classes. You use a nested class when the class makes little sense outside the context of its containing class.

Consider a class that handles the command-line options of a program. Such a class is generally unique to each program, so there is no reason to make a CommandLine class accessible from outside the class that contains Main(). Listing 5.45 demonstrates such a nested class.

LISTING 5.45: Defining a Nested Class


// CommandLine is nested within Program
class Program                                                                      
{                                                                                  
  // Define a nested class for processing the command line.                        
  private class CommandLine                                                        
  {                                                                                
      public CommandLine(string[] arguments)
      {
          for(int argumentCounter=0;
              argumentCounter<arguments.Length;
              argumentCounter++)
          {
              switch (argumentCounter)
              {
                  case 0:
                      Action = arguments[0].ToLower();
                      break;
                  case 1:
                      Id = arguments[1];
                      break;
                  case 2:
                      FirstName = arguments[2];
                      break;
                  case 3:
                      LastName = arguments[3];
                      break;
              }
          }
      }
      public string Action;
      public string Id;
      public string FirstName;
      public string LastName;
  }

  static void Main(string[] args)
  {
    CommandLine commandLine = new CommandLine(args);                               

    switch (commandLine.Action)
    {
        case "new":
            // Create a new employee
            // ...
            break;
        case "update":
            // Update an existing employee's data
            // ...
            break;
        case "delete":
            // Remove an existing employee's file.
            // ...
            break;
        default:
            Console.WriteLine(
                "Employee.exe " +
         "new|update|delete <id> [firstname] [lastname]");
            break;
    }
  }
}


The nested class in this example is Program.CommandLine. As with all class members, no containing class identifier is needed from inside the containing class, so you can simply refer to it as CommandLine.

One unique characteristic of nested classes is the ability to specify private as an access modifier for the class itself. Because the purpose of this class is to parse the command line and place each argument into a separate field, Program.CommandLine is relevant only to the Program class in this application. The use of the private access modifier defines the intended accessibility of the class and prevents access from outside the class. You can do this only if the class is nested.

The this member within a nested class refers to an instance of the nested class, not the containing class. One way for a nested class to access an instance of the containing class is if the containing class instance is explicitly passed, such as via a constructor or method parameter.

Another interesting characteristic of nested classes is that they can access any member on the containing class, including private members. The converse is not true, however: It is not possible for the containing class to access a private member of the nested class.

Nested classes are rare. They should not be defined if they are likely to be referenced outside the containing type. Furthermore, treat public nested classes with suspicion; they indicate potentially poor code that is likely to be confusing and hard to discover.


Guidelines

AVOID publicly exposed nested types. The only exception is if the declaration of such a type is unlikely or pertains to an advanced customization scenario.



Language Contrast: Java—Inner Classes

Java includes not only the concept of a nested class, but also the concept of an inner class. Inner classes correspond to objects that are associated with the containing class instance rather than just a syntactic relationship. In C#, you can achieve the same structure by including an instance field of a nested type within the outer class. A factory method or constructor can ensure a reference to the corresponding instance of the outer class is set within the inner class instance as well.


Partial Classes

Begin 2.0

Another language feature added in C# 2.0 is partial classes. Partial classes are portions of a class that the compiler can combine to form a complete class. Although you could define two or more partial classes within the same file, the general purpose of a partial class is to allow the splitting of a class definition across multiple files. Primarily this is useful for tools that are generating or modifying code. With partial classes, the tools can work on a file separate from the one the developer is manually coding.

Defining a Partial Class

C# 2.0 (and later) allows declaration of a partial class by prepending a contextual keyword, partial, immediately before class, as Listing 5.46 shows.

LISTING 5.46: Defining a Partial Class


// File: Program1.cs
partial class Program
{
}


// File: Program2.cs
partial class Program
{
}


In this case, each portion of Program is placed into a separate file, as identified by the comment.

Besides their use with code generators, another common use of partial classes is to place any nested classes into their own files. This is in accordance with the coding convention that places each class definition within its own file. For example, Listing 5.47 places the Program.CommandLine class into a file separate from the core Program members.

LISTING 5.47: Defining a Nested Class in a Separate Partial Class


// File: Program.cs
partial class Program
{
  static void Main(string[] args)
  {
    CommandLine commandLine = new CommandLine(args);

    switch (commandLine.Action)
    {
       // ...
    }
  }
}


// File: Program+CommandLine.cs
partial class Program
{
  // Define a nested class for processing the command line.
  private class CommandLine
  {
     // ...
  }
}


Partial classes do not allow for extending compiled classes, or classes in other assemblies. They are simply a means of splitting a class implementation across multiple files within the same assembly.

End 2.0

Partial Methods

Begin 3.0

Beginning with C# 3.0, the language designers added the concept of partial methods, extending the partial class concept of C# 2.0. Partial methods are allowed only within partial classes, and like partial classes, their primary purpose is to accommodate code generation.

Consider a code generation tool that generates the Person.Designer.cs file for the Person class based on a Person table within a database. This tool examines the table and creates properties for each column in the table. The problem, however, is that frequently the tool cannot generate any validation logic that may be required because this logic is based on business rules that are not embedded into the database table definition. To overcome this difficulty, the developer of the Person class needs to add the validation logic. It is undesirable to modify Person.Designer.cs directly, because if the file is regenerated (to accommodate an additional column in the database, for example), the changes would be lost. Instead, the structure of the code for Person needs to be separated out so that the generated code appears in one file and the custom code (with business rules) is placed into a separate file unaffected by any regeneration. As we saw in the preceding section, partial classes are well suited for the task of splitting a class across multiple files, but they are not always sufficient. In many cases, we also need partial methods

Partial methods allow for a declaration of a method without requiring an implementation. However, when the optional implementation is included, it can be located in one of the sister partial class definitions, likely in a separate file. Listing 5.48 shows the partial method declaration and the implementation for the Person class.

LISTING 5.48: Defining a Nested Class in a Separate Partial Class


// File: Person.Designer.cs
public partial class Person
{
    #region Extensibility Method Definitions
    partial void OnLastNameChanging(string value);                              
    partial void OnFirstNameChanging(string value);                             
    #endregion

    // ...
    public System.Guid PersonId
    {
        // ...
    }
    private System.Guid _PersonId;

    // ...
    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            if ((_LastName != value))
            {
                OnLastNameChanging(value);                                      
                _LastName = value;
            }
        }
    }
    private string _LastName;

    // ...
    public string FirstName
    {
        get
        {
            return _FirstName;
        }
        set
        {
            if ((_FirstName != value))
            {
                OnFirstNameChanging(value);                                     
                _FirstName = value;
            }
        }
    }
    private string _FirstName;

}


// File: Person.cs
partial class Person
{
  partial void OnLastNameChanging(string value)
  {
      if (value == null)
      {
          throw new ArgumentNullException("value");
      }
      if(value.Trim().Length == 0)
      {
          throw new ArgumentException(
              "LastName cannot be empty.",
              "value");
      }
  }
}


In the listing of Person.Designer.cs are declarations for the OnLastNameChanging() and OnFirstNameChanging() methods. Furthermore, the properties for the last and first names make calls to their corresponding changing methods. Even though the declarations of the changing methods contain no implementation, this code will successfully compile. The key is that the method declarations are prefixed with the contextual keyword partial in addition to the class that contains such methods.

In Listing 5.48, only the OnLastNameChanging() method is implemented. In this case, the implementation checks the suggested new LastName value and throws an exception if it is not valid. Notice that the signatures for OnLastNameChanging() between the two locations match.

Any partial method must return void. If the method didn’t return void and the implementation was not provided, what would the expected return be from a call to a nonimplemented method? To avoid any invalid assumptions about the return, the C# designers decided to prohibit methods with returns other than void. Similarly, out parameters are not allowed on partial methods. If a return value is required, ref parameters may be used.

In summary, partial methods allow generated code to call methods that have not necessarily been implemented. Furthermore, if there is no implementation provided for a partial method, no trace of the partial method appears in the CIL. This helps keep code size small while keeping flexibility high.

End 3.0

Summary

This chapter explained C# constructs for classes and object orientation in C#. Its coverage included a discussion of fields, and a discussion of how to access them on a class instance.

This chapter also discussed the key decision of whether to store data on a per-instance basis or across all instances of a type. Static data is associated with the class, and instance data is stored on each object.

In addition, the chapter explored encapsulation in the context of access modifiers for methods and data. The C# construct of properties was introduced, and you saw how to use it to encapsulate private fields.

The next chapter focuses on how to associate classes with each other via inheritance, and explores the benefits derived from this object-oriented construct.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset