5. Classes

You briefly saw in Chapter 1 how to declare a new class called HelloWorld. In Chapter 2, you learned about the built-in primitive types included with C#. Since you have now also learned about control flow and how to declare methods, it is time to discuss defining your own types. This is the core construct of any C# program; this support for classes and the objects created from them is what makes C# an object-oriented language.

Image

This chapter introduces the basics of object-oriented programming using C#. A key focus is on how to define classes, which are the templates for objects themselves.

All of the constructs of structured programming from the previous chapters still apply within object-oriented programming. However, by wrapping those constructs within classes, you can create larger, more organized programs that are more maintainable. The transition from structured, control-flow-based programs to object-oriented programs revolutionized programming because it provided an extra level of organization. The result was that smaller programs were simplified somewhat. Even more importantly, it was easier to create much larger programs because the code within those programs was better organized.

One of the key advantages of object-oriented programming is that instead of creating new programs entirely from scratch, you can assemble a collection of existing objects from prior work, extending the classes with new features, adding more classes, and thereby providing new functionality.

Readers unfamiliar with object-oriented programming should read the Beginner Topic blocks for an introduction. The general text outside the Beginner Topics focuses on using C# for object-oriented programming with the assumption that readers are already familiar with object-oriented concepts.

This chapter delves into how C# supports encapsulation through its support of constructs such as classes, properties, and access modifiers; we covered methods in the preceding chapter. The next chapter builds on this foundation with the introduction of inheritance and the polymorphism that object-oriented programming enables.

Declaring and Instantiating a Class

Defining a class involves first specifying the keyword class, followed by an identifier, as shown in Listing 5.1.

LISTING 5.1: Defining a Class


class Employee
{
}


All code that belongs to the class will appear between the curly braces following the class declaration. Although not a requirement, generally you place each class into its own file. This makes it easier to find the code that defines a particular class, because the convention is to name the file using the class name.


Guidelines

DO NOT place more than one class in a single source file.

DO name the source file with the name of the public type it contains.


Once you have defined a new class, you can use that class as though it were built into the framework. In other words, you can declare a variable of that type or define a method that takes a parameter of the new class type. Listing 5.2 demonstrates such declarations.

LISTING 5.2: Declaring Variables of the Class Type


class Program
{
  static void Main()
  {
      Employee employee1, employee2;
      // ...
  }

  static void IncreaseSalary(Employee employee)
  {
      // ...
  }
}


Now that you have defined a new class type, it is time to instantiate an object of that type. Mimicking its predecessors, C# uses the new keyword to instantiate an object (see Listing 5.3).

LISTING 5.3: Instantiating a Class


class Program
{
  static void Main()
  {
      Employee employee1 = new Employee();                                
      Employee employee2;
      employee2 = new Employee();                                         

      IncreaseSalary(employee1);
  }
}


Not surprisingly, the assignment can occur in the same statement as the declaration, or in a separate statement.

Unlike the primitive types you have worked with so far, there is no literal way to specify an Employee. Instead, the new operator provides an instruction to the runtime to allocate memory for an Employee object, instantiate the object, and return a reference to the instance.

Although an explicit operator for allocating memory exists, there is no such operator for de-allocating the memory. Instead, the runtime automatically reclaims the memory sometime after the object becomes inaccessible. The garbage collector is responsible for the automatic de-allocation. It determines which objects are no longer referenced by other active objects and then de-allocates the memory for those objects. The result is that there is no compile-time–determined program location where the memory will be collected and restored to the system.

In this trivial example, no explicit data or methods are associated with an Employee, which renders the object essentially useless. The next section focuses on adding data to an object.


Language Contrast: C++—delete Operator

C# programmers should view the new operator as a call to instantiate an object, not as a call to allocate memory. Both objects allocated on the heap and objects allocated on the stack support the new operator, emphasizing the point that new is not about how memory allocation should take place and whether de-allocation is necessary.

Thus C# does not need the delete operator found in C++. Memory allocation and de-allocation are details that the runtime manages, allowing the developer to focus more on domain logic. However, though memory is managed by the runtime, the runtime does not manage other resources such as database connections, network ports, and so on. Unlike C++, C# does not support implicit deterministic resource cleanup (the occurrence of implicit object destruction at a compile-time–defined location in the code). Fortunately, C# does support explicit deterministic resource cleanup via a using statement, and implicit nondeterministic resource cleanup using finalizers.


Instance Fields

One of the key aspects of object-oriented design is the grouping of data to provide structure. This section discusses how to add data to the Employee class. The general object-oriented term for a variable that stores data within a class is member variable. This term is well understood in C#, but the more standard term and the one used in the specification is field, which is a named unit of storage associated with the containing type. Instance fields are variables declared at the class level to store data associated with an object. Hence, association is the relationship between the field data type and the containing field.

Declaring an Instance Field

In Listing 5.4, the class Employee has been modified to include three fields: FirstName, LastName, and Salary.

LISTING 5.4: Declaring Fields


class Employee
{
  public string FirstName;
  public string LastName;
  public string Salary;
}


With these fields added, it is possible to store some fundamental data with every Employee instance. In this case, you prefix the fields with an access modifier of public. The use of public on a field indicates that the data within the field is accessible from classes other than Employee (see the section “Access Modifiers,” later in this chapter).

As with local variable declarations, a field declaration includes the data type to which the field refers. Furthermore, it is possible to assign fields an initial value at declaration time, as demonstrated with the Salary field in Listing 5.5.

LISTING 5.5: Setting Initial Values of Fields at Declaration Time


class Employee
{
  public string FirstName;
  public string LastName;
  public string Salary = "Not enough";                            
}


We delay the guidelines of naming and coding fields until later in the chapter, after C# properties have been introduced. Suffice it to say, Listing 5.5 does not follow the general convention.

Accessing an Instance Field

You can set and retrieve the data within fields. However, the fact that a field does not include a static modifier indicates that it is an instance field. You can access an instance field only from an instance of the containing class (an object). You cannot access it from the class directly (without first creating an instance, in other words).

Listing 5.6 shows an updated look at the Program class and its utilization of the Employee class, and Output 5.1 shows the results.

LISTING 5.6: Accessing Fields


class Program
{
  static void Main()
  {
      Employee employee1 = new Employee();
      Employee employee2;
      employee2 = new Employee();

      employee1.FirstName = "Inigo";                                          
      employee1.LastName = "Montoya";                                         
      employee1.Salary = "Too Little";                                        
      IncreaseSalary(employee1);                                              
      Console.WriteLine(                                                      
          "{0} {1}: {2}",                                                     
          employee1.FirstName,                                                
          employee1.LastName,                                                 
          employee1.Salary);                                                  
      // ...
  }

  static void IncreaseSalary(Employee employee)
  {
      employee.Salary = "Enough to survive on";                           
  }
}


OUTPUT 5.1

Inigo Montoya: Enough to survive on

Listing 5.6 instantiates two Employee objects, as you saw before. Next, it sets each field, calls IncreaseSalary() to change the salary, and then displays each field associated with the object referenced by employee1.

Notice that you first have to specify which Employee instance you are working with. Therefore, the employee1 variable appears as a prefix to the field name when assigning and accessing the field.

Instance Methods

One alternative to formatting the names in the WriteLine() method call within Main() is to provide a method in the Employee class that takes care of the formatting. Changing the functionality to be within the Employee class rather than a member of Program is consistent with the encapsulation of a class. Why not group the methods relating to the employee’s full name with the class that contains the data that forms the name?

Listing 5.7 demonstrates the creation of such a method.

LISTING 5.7: Accessing Fields from within the Containing Class


class Employee
{
  public string FirstName;
  public string LastName;
  public string Salary;

  public string GetName()                                           
  {                                                                 
      return $"{ FirstName }  { LastName }";                        
  }                                                                 
}


There is nothing particularly special about this method compared to what you learned in Chapter 4, except that now the GetName() method accesses fields on the object instead of just local variables. In addition, the method declaration is not marked with static. As you will see later in this chapter, static methods cannot directly access instance fields within a class. Instead, it is necessary to obtain an instance of the class to call any instance member, whether a method or a field.

Given the addition of the GetName() method, you can update Program.Main() to use the method, as shown in Listing 5.8 and Output 5.2.

LISTING 5.8: Accessing Fields from outside the Containing Class


class Program
{
  static void Main()
  {
      Employee employee1 = new Employee();
      Employee employee2;
      employee2 = new Employee();

      employee1.FirstName = "Inigo";
      employee1.LastName = "Montoya";
      employee1.Salary = "Too Little";
      IncreaseSalary(employee1);
      Console.WriteLine(                                                      
          $"{ employee1.GetName() }: { employee1.Salary }");                  
      // ...
  }
  // ...
}


OUTPUT 5.2

Inigo Montoya: Enough to survive on

Using the this Keyword

You can obtain the reference to a class from within instance members that belong to the class. To indicate explicitly that the field or method accessed is an instance member of the containing class in C#, you use the keyword this. Use of this is implicit when calling any instance member, and it returns an instance of the object itself.

For example, consider the SetName() method shown in Listing 5.9.

LISTING 5.9: Using this to Identify the Field’s Owner Explicitly


class Employee
{
  public string FirstName;
  public string LastName;
  public string Salary;

  public string GetName()
  {
      return $"{ FirstName }  { LastName }";
  }

  public void SetName(                                                          
      string newFirstName, string newLastName)                                  
  {                                                                             
      this.FirstName = newFirstName;                                            
      this.LastName = newLastName;                                              
  }                                                                             
}


This example uses the keyword this to indicate that the fields FirstName and LastName are instance members of the class.

Although the this keyword can prefix any and all references to local class members, the general guideline is not to clutter code when there is no additional value. Therefore, you should avoid using the this keyword unless it is required. Listing 5.12 (later in this chapter) is an example of one of the few circumstances when such a requirement exists. Listings 5.9 and 5.10, however, are not good examples. In Listing 5.9, this can be dropped entirely without changing the meaning of the code. And in Listing 5.10 (presented next), by changing the naming convention for fields, we can avoid any ambiguity between local variables and fields.


Language Contrast: Visual Basic—Accessing a Class Instance with Me

The C# keyword this is identical to the Visual Basic keyword Me.


In Listing 5.9 and Listing 5.10, the this keyword is not used in the GetName() method—it is optional. However, if local variables or parameters exist with the same name as the field (see the SetName() method in Listing 5.10), omitting this would result in accessing the local variable/parameter when the intention was the field; given this scenario, use of this is required.

You also can use the keyword this to access a class’s methods explicitly. For example, this.GetName() is allowed within the SetName() method, permitting you to print out the newly assigned name (see Listing 5.11 and Output 5.3).

LISTING 5.11: Using this with a Method


class Employee
{
  // ...

  public string GetName()
  {
      return $"{ FirstName }  { LastName }";
  }

  public void SetName(string newFirstName, string newLastName)
  {
      this.FirstName = newFirstName;
      this.LastName = newLastName;
      Console.WriteLine(                                                          
          $"Name changed to '{ this.GetName() }'");                               
  }
}


class Program
{
  static void Main()
  {
      Employee employee = new Employee();

      employee.SetName("Inigo", "Montoya");
      // ...
  }
  // ...
}


OUTPUT 5.3

Name changed to 'Inigo Montoya'

Sometimes it may be necessary to use this to pass a reference to the currently executing object. Consider the Save() method in Listing 5.12.

LISTING 5.12: Passing this in a Method Call


class Employee
{
  public string FirstName;
  public string LastName;
  public string Salary;

  public void Save()
  {
      DataStorage.Store(this);                                                      
  }
}


class DataStorage
{
  // Save an employee object to a file
  // named with the Employee name.
  public static void Store(Employee employee)
  {
      // ...
  }
}


The Save() method in Listing 5.12 calls a method on the DataStorage class, called Store(). The Store() method, however, needs to be passed the Employee object, which needs to be persisted. This is done using the keyword this, which passes the instance of the Employee object on which Save() was called.

Access Modifiers

When declaring a field earlier in the chapter, you prefixed the field declaration with the keyword public. public is an access modifier that identifies the level of encapsulation associated with the member it decorates. Five access modifiers are available: public, private, protected, internal, and protected internal. This section considers the first two.

The purpose of an access modifier is to provide encapsulation. By using public, you explicitly indicate that it is acceptable that the modified fields are accessible from outside the Employee class—in other words, that they are accessible from the Program class, for example.

Consider an Employee class that includes a Password field, however. It should be possible to call an Employee object and verify the password using a Logon() method. Conversely, it should not be possible to access the Password field on an Employee object from outside the class.

To define a Password field as hidden and inaccessible from outside the containing class, you use the keyword private for the access modifier, in place of public (see Listing 5.15). As a result, the Password field is not accessible from inside the Program class, for example.

LISTING 5.15: Using the private Access Modifier


class Employee
{
  public string FirstName;
  public string LastName;
  public string Salary;
  private string Password;                                                           
  private bool IsAuthenticated;                                                      

  public bool Logon(string password)                                                 
  {                                                                                  
      if(Password == password)                                                       
      {                                                                              
          IsAuthenticated = true;                                                    
      }                                                                              
      return IsAuthenticated;                                                        
  }                                                                                  
                                                                                     
  public bool GetIsAuthenticated()                                                   
  {                                                                                  
      return IsAuthenticated;                                                        
  }                                                                                  
  // ...
}


class Program
{
  static void Main()
  {
      Employee employee = new Employee();

      employee.FirstName = "Inigo";
      employee.LastName = "Montoya";

      // ...

      // Password is private, so it cannot be                                       
      // accessed from outside the class.                                           
      // Console.WriteLine(                                                         
      //    ("Password = {0}", employee.Password);                                  
  }
  // ...
}


Although this option is not shown in Listing 5.15, it is possible to decorate a method with an access modifier of private as well.

If no access modifier is placed on a class member, the declaration defaults to private. In other words, members are private by default and programmers need to specify explicitly that a member is to be public.

Properties

The preceding section, “Access Modifiers,” demonstrated how you can use the private keyword to encapsulate a password, preventing access from outside the class. This type of encapsulation is often too strict, however. For example, sometimes you might need to define fields that external classes can only read, but whose values you can change internally. Alternatively, perhaps you want to allow access to write some data in a class, but you need to be able to validate changes made to the data. In yet another scenario, perhaps you need to construct the data on the fly. Traditionally, languages enabled the features found in these examples by marking fields as private and then providing getter and setter methods for accessing and modifying the data. The code in Listing 5.16 changes both FirstName and LastName to private fields. Public getter and setter methods for each field allow their values to be accessed and changed.

LISTING 5.16: Declaring Getter and Setter Methods


class Employee
{

  private string FirstName;
  // FirstName getter
  public string GetFirstName()
  {
      return FirstName;
  }
  // FirstName setter
  public void SetFirstName(string newFirstName)
  {
      if(newFirstName != null && newFirstName != "")
      {
          FirstName = newFirstName;
      }
  }

  private string LastName;
  // LastName getter
  public string GetLastName()
  {
      return LastName;
  }
  // LastName setter
  public void SetLastName(string newLastName)
  {
      if(newLastName != null && newLastName != "")
      {
          LastName = newLastName;
      }
  }
  // ...
}


Unfortunately, this change affects the programmability of the Employee class. No longer can you use the assignment operator to set data within the class, nor can you access the data without calling a method.

Declaring a Property

Recognizing the frequency of this type of pattern, the C# designers provided explicit syntax for it. This syntax is called a property (see Listing 5.17 and Output 5.5).

LISTING 5.17: Defining Properties


class Program
{
  static void Main()
  {
      Employee employee = new Employee();

      // Call the FirstName property's setter.
      employee.FirstName = "Inigo";

      // Call the FirstName property's getter.
      System.Console.WriteLine(employee.FirstName);
  }
}


class Employee
{
  // FirstName property                                                      
  public string FirstName                                                    
  {                                                                          
      get                                                                    
      {                                                                      
          return _FirstName;                                                 
      }                                                                      
      set                                                                    
      {                                                                      
          _FirstName = value;                                                
      }                                                                      
  }                                                                          
  private string _FirstName;                                                 
                                                                             
  // LastName property                                                       
  public string LastName                                                     
  {                                                                          
      get                                                                    
      {                                                                      
          return _LastName;                                                  
      }                                                                      
      set                                                                    
      {                                                                      
          _LastName = value;                                                 
      }                                                                      
  }                                                                          
  private string _LastName;                                                  
  // ...
}


OUTPUT 5.5

Inigo

The first thing to notice in Listing 5.17 is not the property code itself, but rather the code within the Program class. Although you no longer have the fields with the FirstName and LastName identifiers, you cannot see this by looking at the Program class. The API for accessing an employee’s first and last names has not changed at all. It is still possible to assign the parts of the name using a simple assignment operator, for example (employee.FirstName = "Inigo").

The key feature is that properties provide an API that looks programmatically like a field. In actuality, no such fields exist. A property declaration looks exactly like a field declaration, but following it are curly braces in which to place the property implementation. Two optional parts make up the property implementation. The get part defines the getter portion of the property. It corresponds directly to the GetFirstName() and GetLastName() functions defined in Listing 5.16. To access the FirstName property, you call employee.FirstName. Similarly, setters (the set portion of the implementation) enable the calling syntax of the field assignment:

employee.FirstName = "Inigo";

Property definition syntax uses three contextual keywords. You use the get and set keywords to identify either the retrieval or the assignment portion of the property, respectively. In addition, the setter uses the value keyword to refer to the right side of the assignment operation. When Program.Main() calls employee.FirstName = "Inigo", therefore, value is set to "Inigo" inside the setter and can be used to assign _FirstName. Listing 5.17’s property implementations are the most commonly used. When the getter is called (such as in Console.WriteLine(employee.FirstName)), the value from the field (_FirstName) is obtained and written to the console.

Begin 3.0

Automatically Implemented Properties

In C# 3.0, property syntax includes a shorthand version. Since a property with a single backing field that is assigned and retrieved by the get and set accessors is so trivial and common (see the implementations of FirstName and LastName), the C# 3.0 compiler (and higher) allows the declaration of a property without any accessor implementation or backing field declaration. Listing 5.18 demonstrates the syntax with the Title and Manager properties, and Output 5.6 shows the results.

LISTING 5.18: Automatically Implemented Properties


class Program
{
  static void Main()
  {
        Employee employee1 =
            new Employee();
        Employee employee2 =
            new Employee();

        // Call the FirstName property's setter.
        employee1.FirstName = "Inigo";

        // Call the FirstName property's getter.
        System.Console.WriteLine(employee1.FirstName);

        // Assign an auto-implemented property
        employee2.Title = "Computer Nerd";
        employee1.Manager = employee2;

        // Print employee1's manager's title.
        System.Console.WriteLine(employee1.Manager.Title);
  }
}


class Employee
{
  // FirstName property
  public string FirstName
  {
      get
      {
          return _FirstName;
      }
      set
      {
          _FirstName = value;
      }
  }
  private string _FirstName;

  // LastName property
  public string LastName
  {
      get
      {
          return _LastName;
      }
      set
      {
          _LastName = value;
      }
  }
  private string _LastName;

  public string Title { get; set; }                               

  public Employee Manager { get; set; }                           

  public string Salary { get; set; } = "Not Enough";              
  // ...
}


OUTPUT 5.6

Inigo
Computer Nerd

Auto-implemented properties provide for a simpler way of writing properties in addition to reading them. Furthermore, when it comes time to add something such as validation to the setter, any existing code that calls the property will not have to change, even though the property declaration will have changed to include an implementation.

Throughout the remainder of the book, we will frequently use this C# 3.0 or later syntax without indicating that it is a feature introduced in C# 3.0.

End 3.0

Begin 6.0

One final thing to note about automatically declared properties is that in C# 6.0, it is possible to initialize them as Listing 5.18 does for Salary:

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

Prior to C# 6.0, property initialization was possible only via a method (including the constructor, as we discuss later in the chapter). However, with C# 6.0, you can initialize automatically implemented properties at declaration time using a syntax much like that used for field initialization.

End 6.0

Property and Field Guidelines

Given that it is possible to write explicit setter and getter methods rather than properties, on occasion a question may arise as to whether it is better to use a property or a method. The general guideline is that methods should represent actions and properties should represent data. Properties are intended to provide simple access to simple data with a simple computation. The expectation is that invoking a property will not be significantly more expensive than accessing a field.

With regard to naming, notice that in Listing 5.18 the property name is FirstName, and the field name changed from earlier listings to _FirstName—that is, PascalCase with an underscore suffix. Other common naming conventions for the private field that backs a property are _firstName and m_FirstName (a holdover from C++, where the m stands for member variable), and on occasion the camelCase convention, just like with local variables.3 The camelCase convention should be avoided, however. The camelCase used for property names is the same as the naming convention used for local variables and parameters, meaning that overlaps in names become highly probable. Also, to respect the principles of encapsulation, fields should not be declared as public or protected.

3. We prefer _FirstName because the m in front of the name is unnecessary when compared with an underscore (_). Also, by using the same casing as the property, it is possible to have only one string within the Visual Studio code template expansion tools, instead of having one for both the property name and the field name.


Guidelines

DO use properties for simple access to simple data with simple computations.

AVOID throwing exceptions from property getters.

DO preserve the original property value if the property throws an exception.

DO favor automatically implemented properties over properties with simple backing fields when no additional logic is required.


Regardless of which naming pattern you use for private fields, the coding standard for properties is PascalCase. Therefore, properties should use the LastName and FirstName pattern with names that represent nouns, noun phrases, or adjectives. It is not uncommon, in fact, that the property name is the same as the type name. Consider an Address property of type Address on a Person object, for example.


Guidelines

CONSIDER using the same casing on a property’s backing field as that used in the property, distinguishing the backing field with an “_” prefix. Do not, however, use two underscores; identifiers beginning with two underscores are reserved for the use of the C# compiler itself.

DO name properties using a noun, noun phrase, or adjective.

CONSIDER giving a property the same name as its type.

AVOID naming fields with camelCase.

DO favor prefixing Boolean properties with “Is,” “Can,” or “Has,” when that practice adds value.

DO NOT declare instance fields that are public or protected. (Instead, expose them via a property.)

DO name properties with PascalCase.

DO favor automatically implemented properties over fields.

DO favor automatically implemented properties over using fully expanded ones if there is no additional implementation logic.


Using Properties with Validation

Notice in Listing 5.19 that the Initialize() method of Employee uses the property rather than the field for assignment as well. Although this is not required, the result is that any validation within the property setter will be invoked both inside and outside the class. Consider, for example, what would happen if you changed the LastName property so that it checked value for null or an empty string, before assigning it to _LastName.

LISTING 5.19: Providing Property Validation


class Employee
{
  // ...
  public void Initialize(
      string newFirstName, string newLastName)
  {
      // Use property inside the Employee
      // class as well.
      FirstName = newFirstName;
      LastName = newLastName;
  }


  // LastName property
  public string LastName
  {
      get
      {
          return _LastName;
      }
      set
      {
          // Validate LastName assignment                                
          if(value == null)                                              
          {                                                              
              // Report error                                            
              // In C# 6.0 replace "value" with nameof(value)            
              throw new ArgumentNullException("value");                  
          }                                                              
          else                                                           
          {                                                              
              // Remove any whitespace around                            
              // the new last name.                                      
              value = value.Trim();                                      
              if(value == "")                                            
              {                                                          
                  // Report error                                        
                  // In C# 6.0 replace "value" with nameof(value)        
                  throw new ArgumentException(                           
                      "LastName cannot be blank.", "value");4            
              }                                                          
              else                                                       
                  _LastName = value;                                     
          }                                                              
      }
  }
  private string _LastName;
  // ...
}


4. Apologies to Teller, Cher, Sting, Madonna, Bono, Prince, Liberace, et al.

With this new implementation, the code throws an exception if LastName is assigned an invalid value, either from another member of the same class or via a direct assignment to LastName from inside Program.Main(). The ability to intercept an assignment and validate the parameters by providing a field-like API is one of the advantages of properties.

It is a good practice to access a property-backing field only from inside the property implementation. In other words, you should always use the property, rather than calling the field directly. In many cases, this principle holds even from code within the same class as the property. If you follow this practice, when you add code such as validation code, the entire class immediately takes advantage of it.5

5. As described later in the chapter, one exception to this occurs when the field is marked as read-only, because then the value can be set only in the constructor. In C# 6.0, you can directly assign the value of a read-only property, completely eliminating the need for the read-only field.

Although rare, it is possible to assign value inside the setter, as Listing 5.19 does. In this case, the call to value.Trim() removes any whitespace surrounding the new last name value.


Guidelines

AVOID accessing the backing field of a property outside the property, even from within the containing class.

DO use “value” for the paramName argument when calling the ArgumentException() or ArgumentNullException() constructor (“value” is the implicit name of the parameter on property setters).


Begin 6.0

End 6.0

Read-Only and Write-Only Properties

By removing either the getter or the setter portion of a property, you can change a property’s accessibility. Properties with only a setter are write-only, which is a relatively rare occurrence. Similarly, providing only a getter will cause the property to be read-only; any attempts to assign a value will cause a compile error. To make Id read-only, for example, you would code it as shown in Listing 5.20.

LISTING 5.20: Defining a Read-Only Property prior to C# 6.0


class Program
{
  static void Main()
  {
      Employee employee1 = new Employee();
      employee1.Initialize(42);

      // ERROR:  Property or indexer 'Employee.Id'                                  
      // cannot be assigned to; it is read-only.                                    
      // employee1.Id = "490";                                                      
  }
}

class Employee
{
  public void Initialize(int id)
  {
      // Use field because Id property has no setter;                                
      // it is read-only.                                                            
      _Id = id.ToString();                                                           
  }

  // ...
  // Id property declaration
  public string Id
  {
      get
      {
          return _Id;
      }
      // No setter provided.                                                         
  }
  private string _Id;

}


Listing 5.20 assigns the field from within the Employee constructor rather than the property (_Id = id). Assigning via the property causes a compile error, as it does in Program.Main().

Begin 6.0

Starting in C# 6.0, there is also support for read-only automatically implemented properties as follows:

      public bool[,,] Cells { get; } = new bool[2, 3, 3];

This is clearly a significant improvement over the pre-C# 6.0 approach, especially given the commonality of read-only properties for something like an array of items or the Id in Listing 5.20.

One important note about a read-only automatically implemented property is that, like read-only fields, the compiler requires that it be initialized in the constructor or via an initializer. In the preceding snippet we use an initializer, but the assignment of Cells from within the constructor is also permitted.

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 there is virtually no need to ever use pre-C# 6.0 syntax; instead, the programmer can always use a read-only, automatically implemented property. The only exception might be when the data type of the read-only modified field does not match the data type of the property—for example, if the field was of type int and the read-only property was of type double.


Guidelines

DO create read-only properties if the property value should not be changed.

DO create read-only automatically implemented properties in C# 6.0 (or later) rather than read-only properties with a backing field if the property value should not be changed


End 6.0

Properties As Virtual Fields

As you have seen, properties behave like virtual fields. In some instances, you do not need a backing field at all. Instead, the property getter returns a calculated value while the setter parses the value and persists it to some other member fields (if it even exists). Consider, for example, the Name property implementation shown in Listing 5.21. Output 5.7 shows the results.

LISTING 5.21: Defining Properties


class Program
{
  static void Main()
  {
      Employee employee1 = new Employee();

      employee1.Name = "Inigo Montoya";                                   
      System.Console.WriteLine(employee1.Name);                           

      // ...
  }
}


class Employee
{
  // ...

  // FirstName property
  public string FirstName
  {
      get
      {
          return _FirstName;
      }
      set
      {
          _FirstName = value;
      }
  }
  private string _FirstName;

  // LastName property
  public string LastName
  {
      get
      {
          return _LastName;
      }
      set
      {
          _LastName = value;
      }
  }
  private string _LastName;
  // ...

  // Name property                                                        
  public string Name                                                      
  {                                                                       
      get                                                                 
      {                                                                   
          return $"{ FirstName } { LastName }";                           
      }                                                                   
      set                                                                 
      {                                                                   
          // Split the assigned value into                                
          // first and last names.                                        
          string[] names;                                                 
          names = value.Split(new char[]{' '});                           
          if(names.Length == 2)                                           
          {                                                               
              FirstName = names[0];                                       
              LastName = names[1];                                        
          }                                                               
          else                                                            
          {                                                               
              // Throw an exception if the full                           
              // name was not assigned.                                   
             throw new System. ArgumentException (                        
                  $"Assigned value '{ value }' is invalid", "value");     
          }                                                               
      }                                                                   
  }
  public string Initials => $"{ FirstName[0] } { LastName[0] }";
  // ...
}


OUTPUT 5.7

Inigo Montoya

The getter for the Name property concatenates the values returned from the FirstName and LastName properties. In fact, the name value assigned is not actually stored. When the Name property is assigned, the value on the right side is parsed into its first and last name parts.

Begin 2.0

Access Modifiers on Getters and Setters

As previously mentioned, it is a good practice not to access fields from outside their properties because doing so circumvents any validation or additional logic that may be inserted. Unfortunately, C# 1.0 did not allow different levels of encapsulation between the getter and setter portions of a property. It was not possible, therefore, to create a public getter and a private setter so that external classes would have read-only access to the property while code within the class could write to the property.

In C# 2.0, support was added for placing an access modifier on either the get or the set portion of the property implementation (not on both), thereby overriding the access modifier specified on the property declaration. Listing 5.22 demonstrates how to do this.

LISTING 5.22: Placing Access Modifiers on the Setter


class Program
{
  static void Main()
  {
      Employee employee1 = new Employee();
      employee1.Initialize(42);
      // ERROR: The property or indexer 'Employee.Id'                 
      // cannot be used in this context because the set               
      // accessor is inaccessible                                     
      employee1.Id = "490";                                           
  }
}


class Employee
{
  public void Initialize(int id)
  {
      // Set Id property                                              
      Id = id.ToString();                                             
  }

  // ...
  // Id property declaration
  public string Id
  {
      get
      {
          return _Id;
      }
      // Providing an access modifier is possible in C# 2.0
      // and higher only
      private set                                                     
      {                                                               
          _Id = value;                                                
      }                                                               
  }
  private string _Id;

}


By using private on the setter, the property appears as read-only to classes other than Employee. From within Employee, the property appears as read/write, so you can assign the property within the constructor. When specifying an access modifier on the getter or setter, take care that the access modifier is more restrictive than the access modifier on the property as a whole. It is a compile error, for example, to declare the property as private and the setter as public.


Guidelines

DO apply appropriate accessibility modifiers on implementations of getters and setters on all properties.

DO NOT provide set-only properties or properties with the setter having broader accessibility than the getter.


End 2.0

Properties and Method Calls Not Allowed As ref or out Parameter Values

C# allows properties to be used identically to fields, except when they are passed as ref or out parameter values. ref and out parameter values are internally implemented by passing the memory address to the target method. However, because properties can be virtual fields that have no backing field, or can be read-only or write-only, it is not possible to pass the address for the underlying storage. As a result, you cannot pass properties as ref or out parameter values. The same is true for method calls. Instead, when code needs to pass a property or method call as a ref or out parameter value, the code must first copy the value into a variable and then pass the variable. Once the method call has completed, the code must assign the variable back into the property.

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

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