Search Forum
(57415 Postings)
Search Site/Articles

Archived Articles
712 Articles

C# Books
C# Consultants
What Is C#?
Download Compiler
Code Archive
Archived Articles
Advertise
Contribute
C# Jobs
Beginners Tutorial
C# Contractors
C# Consulting
Links
C# Manual
Contact Us
Legal

GoDiagram for .NET from Northwoods Software www.nwoods.com


              
Printable Version

Designing Object-Oriented Programs In C#
By Joe Mayo

In This Chapter

  • Inheritance
  • Encapsulating Object Internals
  • Polymorphism

C# is a modern object-oriented programming language. As such it has many new features to support object-oriented programming. The preceding chapter covered the proper syntax of classes and their members. This chapter takes you a step further. It builds upon what has already been presented to create object-oriented programs.

This chapter discusses inheritance, the capability to derive new classes from existing ones. It solidifies what has been presented about encapsulation. Then it examines the nuances of polymorphism, allowing classes to dynamically modify their runtime behavior. This chapter provides a basis for how to do detailed design of object-oriented programs with C# as an implementation language.

Inheritance

Inheritance is an object-oriented term relating to how one class, a derived class, can share the characteristics and behavior from another class, a base class. There should be a natural parent/child relationship between the base class and the derived class, respectively. This can be thought of as an "is a" relationship, because the derived class can be identified by both its class type and its base class type. Essentially, any base class members with protected or greater access also belong to a derived class.

The benefits gained by this are the ability to reuse the base class members and also to add members to the derived class. The derived class then becomes a specialization of the parent. This specialization can continue for as many levels as necessary, each new level derived from the base class above it. In the opposite direction, going up the inheritance hierarchy, there is more generalization at each new base class traversed. Regardless of how many levels between classes, the "is a" relationship holds.

Base Classes

Normal base classes may be instantiated themselves, or inherited. Derived classes inherit each base class member marked with protected or greater access. The derived class is specialized to provide more functionality, in addition to what its base class provides.

A derived class declares that it inherits from a base class by adding a colon (:) and the base class name after the derived class name. Here's an example:

public class Contact
{
  string name;
  string email;
  string address;

  public Contact()
  {
    // statements ...
  }

  public string Name
  {
    get
    {
      return name;
    }
    set
    {
      name = value;
    }
  }

  public string Email
  {
    get
    {
      return email;
    }
    set
    {
      email = value;
    }
  }

  public string Address
  {
    get
    {
      return address;
    }
    set
    {
      address = value;
    }
  }
}

public class Customer : Contact
{
  string gender;
  decimal income;

  public Customer()
  {
    // statements ...
  }
}

In the example, the Contact class is inherited by the Customer class. This means the Customer class possesses all the same members as its base class (Contact) in addition to its own. In this case, Customer has the properties Name, Email, and Address.

Since Customer is a specialization of Contact, it has its own unique members: gender and income.

Abstract Classes

Abstract classes are a special type of base classes. In addition to normal class members, they have abstract class members. These Abstract class members are methods and properties that are declared without an implementation. All classes derived directly from abstract classes must implement these abstract methods and properties.

Abstract classes can never be instantiated. This would be illogical, because of the members without implementations. So what good is a class that can't be instantiated? Lots! Abstract classes sit toward the top of a class hierarchy. They establish structure and meaning to code. They make frameworks easier to build. This is possible because abstract classes have information and behavior common to all derived classes in a framework. Take a look at the following example:

abstract public class Contact
{
  protected string name;

  public Contact()
  {
    // statements...
  }

  public abstract void generateReport();

  abstract public string Name
  {
    get;
    set;
  }}

public class Customer : Contact
{
  string gender;
  decimal income;
  int numberOfVisits;

  public Customer()
  {
    // statements
  }

  public override void generateReport()
  {
    // unique report
  }

  public override string Name
  {
    get
    {
      numberOfVisits++;
      return name;
    }
    set
    {
      name = value;
      numberOfVisits = 0;
    }
  }
}

public class SiteOwner : Contact
{
  int siteHits;
  string mySite;

  public SiteOwner()
  {
    // statements...
  }

  public override void generateReport()
  {
    // unique report
  }

  public override string Name
  {
    get
    {
      siteHits++;
      return name;
    }
    set
    {
      name = value;
      siteHits = 0;
    }
  }
}

This example has three classes. The first class, Contact, is now an abstract class. This is shown as the first modifier of its class declaration. Contact has two abstract members, and it has an abstract method named generateReport(). This method is declared with the abstract modifier in front of the method declaration. It has no implementation (no braces) and is terminated with a semicolon. The Name property is also declared abstract. The accessors of properties are terminated with semicolons.

The abstract base class Contact has two derived classes, Customer and SiteOwner. Both of these derived classes implement the abstract members of the Contact class. The generateReport() method in each derived class has an override modifier in its declaration. Likewise, the Name declaration contains an override modifier in both Customer and SiteOwner.

The override modifier for the overridden generateReport() method and Name property is mandatory. C# requires explicit declaration of intent when overriding methods. This feature promotes safe code by avoiding the accidental overriding of base class methods, which is what actually does happen in other languages. Leaving out the override modifier generates an error. Similarly, adding a new modifier also generates an error. Abstract methods must be overridden and cannot be hidden, which the new modifier or the lack of a modifier would be trying to do.

Notice the name field in the Contact class. It has a protected modifier. Remember, a protected modifier allows derived classes to access base class members. In this case, it enables the overridden Name property to access the name field in the Contact class.

The most famous of all abstract classes is the Object class. It may be referred to as object or Object, but it's still the same class. Object is the base class for all other classes in C#. It's also the default base class when a base class is not specified. The following class declarations produce the same exact results:

abstract public class Contact : Object
{
  // class members
}

abstract public class Contact
{
  // class members
}

Object is implicitly included as a base class if it is not already declared. Besides providing the abstract glue to hold together the C# class framework, object includes built-in functionality, some of which is useful for derived classes to implement. Table 8.1 lists each object method and its purpose.

Table 8.1 Object Class Methods

Method

Purpose

Equals()

Compares object references for equality.

GetHashCode()

Returns a hash code for an object.

GetType()

Returns the type of the object.

ToString()

Returns a string representation of an object.

Finalize()

Same as a destructor.

MemberwiseClone()

Performs shallow copy of an object.


All of the methods in Table 8.1 are public, except for Finalize() and MemberwiseClone(), which are protected. The GetType() and MemberwiseClone() methods may not be overridden, but all others may. Listing 8.1 shows an example of using object methods.

Listing 8.1 Object Class Member Implementations in a Derived Class

using System;

public class WebSite
{
  public string SiteName;
  public string URL;
  public string Description;
  public WebSite()
  {
  }

  public WebSite( string strSiteName, string strURL, string strDescription )
  {
    SiteName  = strSiteName;
    URL     = strURL;
    Description = strDescription;
  }
}

abstract public class Contact
{
  protected string name;

  public Contact()
  {
    // initialization code...
  }

  public abstract string generateReport();

  abstract public string Name
  {
    get;
    set;
  }
}

public class SiteOwner : Contact
{
  int siteHits;
  WebSite mySite;

  public SiteOwner()
  {
    mySite = new WebSite();
    siteHits = 0;
  }

  public SiteOwner(string aName, WebSite aSite)
  {
    mySite = new WebSite(aSite.SiteName,
               aSite.URL,
               aSite.Description);

    Name = aName;
  }

  public override string generateReport()
  {
    return this.ToString();
  }

  public override string Name
  {
    get
    {
      siteHits++;
      return name;
    }
    set
    {
      name = value;
      siteHits = 0;
    }
  }

  public override string ToString()
  {
    return "["         +
        Name        +
        ", "        +
        siteHits.ToString() +
        "]";
  }

  public override bool Equals(Object anOwner)
  {
    return this.ToString().Equals(anOwner.ToString());
  }

  public override int GetHashCode()
  {
    return this.ToString().GetHashCode();
  }

  public SiteOwner Clone()
  {
    return (SiteOwner) this.MemberwiseClone();
  }
}

public class Test
{
  public Test() {}

  public static void Main()
  {
    WebSite mySite = new WebSite("Le Financier",
                   "http://www.LeFinancier.com",
                   "Fancy Financial Site");

    SiteOwner firstOwner = new SiteOwner("Jack", mySite);
    SiteOwner secondOwner = firstOwner.Clone();

    Console.WriteLine("Report:  {0}",
      firstOwner.generateReport());
    Console.WriteLine("To String: {0}",
      firstOwner.ToString());
    Console.WriteLine("Hash Code: {0}",
      firstOwner.GetHashCode());

    Console.WriteLine("Report:  {0}",
      secondOwner.generateReport());
    Console.WriteLine("To String: {0}",
      secondOwner.ToString());
Console.WriteLine("Hash Code: {0}",
      secondOwner.GetHashCode());

    Console.WriteLine(
      "1stOwner: {0} equals: {1} 2ndOwner: {2}.",
      firstOwner.Name,
      firstOwner.Equals(secondOwner),
      secondOwner.Name);

    Console.WriteLine(
      "2nd Equality Check: {0}",
      firstOwner.Equals(secondOwner));

    Console.WriteLine("Report:  {0}",
      firstOwner.generateReport());
    Console.WriteLine("To String: {0}",
      firstOwner.ToString());
    Console.WriteLine("Hash Code: {0}",
      firstOwner.GetHashCode());

    Console.WriteLine("Report:  {0}",
      secondOwner.generateReport());
    Console.WriteLine("To String: {0}",
      secondOwner.ToString());
    Console.WriteLine("Hash Code: {0}",
      secondOwner.GetHashCode());
  }
}

And here's its output:

Report:  [Jack, 1]
To String: [Jack, 2]
Hash Code: 179554879
Report:  [Jack, 1]
To String: [Jack, 2]
Hash Code: 179554879
1stOwner: Jack equals: False 2ndOwner: Jack.
2nd Equality Check: True
Report:  [Jack, 7]
To String: [Jack, 8]
Hash Code: 179555189
Report:  [Jack, 7]
To String: [Jack, 8]
Hash Code: 179555189

Listing 8.1 contains three classes to show implementation of object methods. The Contact class is the base class for the SiteOwner class. Although the Contact class possesses functionality and abstract definitions, it does not contain overridden methods from the Object class. The SiteOwner class does have the overridden methods from the Object class. This shows that a class doesn't have to inherit directly from a base class to override its members.

The SiteOwner class overrides three of Object's methods, ToString(), Equals(), and GetHashCode(). Each of these methods has the override modifier in its declaration. The Object class method definitions for these members are not invoked because the Object class method definitions are overridden by SiteOwner.

The ToString() method returns a string representation of an object's contents. This is often useful for debugging where the contents of an object are dumped to an error log file or perhaps to the console. In this case, ToString() concatenates Name and siteHits and formats them into a string to be returned to the calling program. This example uses the ToString() method extensively.

The Equals() method compares the current SiteOwner's ToString() method to the value of the parameter's ToString() method. This comparison takes advantage of the built-in capabilities of the string type.

The GetHashCode() method executes the ToString() method of the current SiteOwner and uses that to get a hash code. A hash value is normally calculated from a key value that is not expected to change within an object. In this case, the process is simplified by using the string type's built-in GetHashCode() method. Hash codes are useful for any function requiring a unique integer value from a class. The most common use of this is with the HashTable collection class.

The Clone() method uses Object's MemberwiseClone() method. The Object class's MemberwiseClone() method can't be overridden and has protected access. Therefore, other objects cannot call this method on an instance of SiteOwner. This is why this method call is wrapped in the Clone() method.

The output of this program is somewhat strange. The first comparison, where firstOwner is compared to secondOwner, fails. However, the second comparison, immediately after that, passes. What gives?

Fortunately, the ToString() printouts provide some clues. The second parameter of the ToString() method output increments from 1 to 2 in the first pair of printouts before the equality checks. After the equality checks, the numbers increment from 7 to 8. Going back to the ToString() method shows this second parameter is the siteHits field.

Further investigation reveals that the only place where the siteHits field is modified is in the get accessor of the Name property. This shows why the number is changing. Every time ToString() executes, it uses the Name property. This invokes the get accessor of Name, which increments siteName. Since, during processing of the ToString() method, the get accessor of Name executes before siteHits is read, the printout never shows siteHits as being 0.

So, to explain the printouts: ToString() is called twice for each object, firstOwner and secondOwner, incrementing siteHits twice. This leaves siteHits at 2 on both objects. In the equality check, firstOwner.Name is accessed, leaving firstOwner.siteHits at 3. Now, secondOwner.siteHits is still 2 because its Name property has not been accessed. Therefore, when the Equals() method is called, these two objects produce different strings and are, in fact, not equal. Finally, secondOwner.Name is accessed, incrementing its siteHits to 3. Now both objects produce the same strings, so when Equals() is called again on the next line it returns true. The rest of the printouts should be understandable after this explanation.


Warning

Properties can have side effects. In the ToString() method of this chapter, it seemed pretty slick to update the siteHit every time the Name property was read. Perhaps some motivation for this would be that every time a site was visited, the Site Owner's name would be referenced. This was a narrow view of how this class could be used. The choice to use the Name property in the ToString() method seemed natural, but the side effect of incrementing the siteHits field caused a potentially serious bug. When building a class, think about how properties will be used.


A couple of Object methods not shown here are GetType() and Finalize(). The GetType() method is shown later in the chapter, when polymorphism is discussed. The Finalize() method is normally never used in a class declaration. The destructor syntax is used instead. Destructors and Finalizers are the same thing. During compilation, C# converts all destructors to the Finalize() method, for compatibility with other languages conforming to Common Language Infrastructure (CLI) standards. This enables the garbage collector to work with Finalize() methods instead of language specific syntax.

Calling Base Class Members

Derived classes can access the members of their base class if those members have protected or greater access. Simply use the member name in the appropriate context, just as if that member were a part of the derived class itself. Here's an example:

abstract public class Contact
{
  private string address;
  private string city;
  private string state;
  private string zip;

  public string FullAddress()
  {
    string fullAddress =
      address + '\n' +
      city + ',' + state + ' ' + zip;

    return fullAddress;
  }
}

public class Customer : Contact
{
  public string GenerateReport()
  {
    string fullAddress = FullAddress();
    // do some other stuff...
    return fullAddress;
  }
}

In this example, the GenerateReport() method of the Customer class calls the FullAddress() method in its base class, Contact. All classes have full access to their own members without qualification. Qualification refers to using a class name with the dot operator to access a class member—MyObject.SomeMethod(), for instance. This shows that a derived class can access its base class members in the same manner as its own.

Base class constructors can be called from derived classes. To call a base class constructor, use the base() constructor reference. This is desirable when it's necessary to initialize a base class appropriately.

Here's an example that shows the derived class constructor with an address parameter:

abstract public class Contact
{
  private string address;

  public Contact(string address)
  {
    this.address = address;
  }
}

public class Customer : Contact
{
  public Customer(string address) : base(address)
  {
  }
}

In this code, the Customer class does not have an address, so it passes the parameter to its base class constructor by adding a colon and the base keyword with the parameter to its declaration. This calls the Contact constructor with the address parameter, where the address field in Contact is initialized.


Warning

Depending on the design of a class hierarchy, failure to initialize base class constructors may leave code in an inconsistent state.


The following example will not compile. It illustrates the effects of not including a default constructor in a class definition:

abstract public class Contact
{
  private string address;

  public Contact(string address)
  {
    this.address = address;
  }
}

public class Customer : Contact
{
  public Customer(string address)
  {
  }
}

In this example, the Customer constructor does not call the base class constructor. This is obviously a bug, since the address field will never be initialized.

When a class has no explicit constructor, the system assigns a default constructor. The default constructor automatically calls a default or parameterless base constructor. Here's an example of automatic default constructor generation that would occur for the preceding example:

public Customer() : Contact()
{
}

When a class does not declare any constructors, the code in this example is automatically generated. The default base class constructor is called implicitly when no derived class constructors are defined. Once a derived class constructor is defined, whether or not it has parameters, a default constructor will not be automatically defined, as the preceding code showed.

Hiding Base Class Members

Sometimes derived class members have the same name as a corresponding base class member. In this case, the derived member is said to be "hiding" the base class member. When hiding occurs, the derived member is masking the functionality of the base class member. Users of the derived class won't be able to see the hidden member; they'll see only the derived class member. The following code shows how hiding a base class member works. If you're compiling this example now, please disregard the compiler warning, which I explain at the start of the next section, "Versioning."

abstract public class Contact
{
  private string address;
  private string city;
  private string state;
  private string zip;

  public string FullAddress()
  {
    string fullAddress =
      address + '\n' +
      city + ',' + state + ' ' + zip;

    return fullAddress;
  }
}

public class SiteOwner : Contact
{
  public string FullAddress()
  {
    string fullAddress;

    // create an address...
    return fullAddress;
  }
}

In this example, both SiteOwner and its base class, Contact, have a method named FullAddress(). The FullAddress() method in the SiteOwner class hides the FullAddress() method in the Contact class. This means that when an instance of a SiteOwner class is invoked with a call to the FullAddress() method, it is the SiteOwner class FullAddress() method that is called, not the FullAddress() method of the Contact class.

Although a base class member may be hidden, the derived class can still access it. It does this through the base identifier. Sometimes this is desirable. It is often useful to take advantage of the base class functionality and then add to it with the derived class code. The next example shows how to refer to a base class method from the derived class. If compiling this code now, please disregard the warnings, which I explain at the start of the next section, "Versioning."

abstract public class Contact
{
  private string address;
  private string city;
  private string state;
  private string zip;

  public string FullAddress()
  {
    string fullAddress =
      address + '\n' +
      city + ',' + state + ' ' + zip;

    return fullAddress;
  }
}

public class SiteOwner : Contact
{
  public string FullAddress()
  {
    string fullAddress = base.FullAddress();

    // do some other stuff...
    return fullAddress;
  }
}

In this particular example, the FullAddress() method of the Contact class is called from within the FullAddress() method of the SiteOwner class. This is accomplished with a base class reference. This provides another way to reuse code and add on to it with customized behavior.

Versioning

Versioning, in the context of inheritance, is a C# mechanism that allows modification of classes (creating new versions) without accidentally changing the meaning of the code. Hiding a base class member with the methods previously described generates a warning message from the compiler. This is because of the C# versioning policy. It's designed to eliminate a class of problems associated with modifications to base classes.


Warning

Often these warning messages scroll off the screen or are overlooked during compilation in an IDE. These overlooked warnings could be early indications of a bug.


Here's the scenario: A developer creates a class that inherits from a third-party library. For the purposes of this discussion, we assume that the Contact class represents the third-party library. Here's the example:

public class Contact
{
  // does not include FullAddress() method
}

public class SiteOwner : Contact
{
  public string FullAddress()
  {
    string fullAddress = mySite.ToString();

    return fullAddress;
  }
}

In this example, the FullAddress() method does not exist in the base class. There is no problem yet. Later on, the creators of the third-party library update their code. Part of this update includes a new member in a base class with the exact same name as the derived class:

public class Contact
{
  private string address;
  private string city;
  private string state;
  private string zip;

  public string FullAddress()
  {
    string fullAddress =
      address + '\n' +
      city + ',' + state + ' ' + zip;

    return fullAddress;
  }
}

public class SiteOwner : Contact
{
  public string FullAddress()
  {
    string fullAddress = mySite.ToString();

    return fullAddress;
  }
}

In this code, the base class method FullAddress() contains different functionality than the derived class method. In other languages, this scenario would break the code because of implicit polymorphism. (Polymorphism is discussed later in this chapter.) However, this does not break any code in C# because when the FullAddress() method is called on SiteOwner, it is still the SiteOwner class method that gets called.

This scenario generates a warning message. One way to eliminate the warning message is to place a new modifier in front of the derived class method name, as the following example shows:

using System;

public class WebSite
{
  public string SiteName;
  public string URL;
  public string Description;

  public WebSite()
  {
  }

  public WebSite( string strSiteName, string strURL, string strDescription )
  {
    SiteName  = strSiteName;
    URL     = strURL;
    Description = strDescription;
  }

  public override string ToString()
  {
    return SiteName + ", " +
        URL   + ", " +
        Description;
  }
}


public class Contact
{
  public string address;
  public string city;
  public string state;
  public string zip;

  public string FullAddress()
  {
    string fullAddress =
      address + '\n' +
      city + ',' + state + ' ' + zip;

    return fullAddress;
  }
}

public class SiteOwner : Contact
{
  int   siteHits;
  string name;
  WebSite mySite;

  public SiteOwner()
  {
    mySite = new WebSite();
    siteHits = 0;
  }

  public SiteOwner(string aName, WebSite aSite)
  {
    mySite = new WebSite(aSite.SiteName,
               aSite.URL,
               aSite.Description);

    Name = aName;
  }

  new public string FullAddress()
  {
    string fullAddress = mySite.ToString();

    return fullAddress;
  }

  public string Name
  {
    get
    {
      siteHits++;
      return name;
    }
    set
    {
      name = value;
      siteHits = 0;
    }
  }
}

public class Test
{
  public static void Main()
  {
    WebSite mySite = new WebSite("Le Financier",
                   "http://www.LeFinancier.com",
                   "Fancy Financial Site");

    SiteOwner anOwner = new SiteOwner("John Doe", mySite);
    string address;

    anOwner.address = "123 Lane Lane";
    anOwner.city  = "Some Town";
    anOwner.state  = "HI";
    anOwner.zip   = "45678";

    address = anOwner.FullAddress(); // Different Results
    Console.WriteLine("Address: \n{0}\n", address);

  }
}

Here's the output:

Address:
Le Financier, http://www.LeFinancier.com, Fancy Financial Site

This has the effect of explicitly letting the compiler know the developer's intent. Placing the new modifier in front of the derived class member states that the developers know there is a base class method with the same name, and they definitely want to hide that member. This prevents breakage of existing code that depends on the implementation of the derived class member. With C#, the method in the derived class is called when an object of the derived class type is used. Likewise, the method in the base class is called when an object of the Base class type is called. Another problem this presents is that the base class may present some desirable new features that wouldn't be available through the derived class.

To use these new features requires one of a few different workarounds. One option would be to rename the derived class member, which would allow programs to use a base class method through a derived class member. The drawback to this option would be if there were other classes relying upon the implementation of the derived class member with the same name. This scenario will break code and, for this reason, is considered extremely bad form.

Another option is to define a new method in the derived class that called the base class method. This allows users of the derived class to have the new functionality of the base class, yet retain their existing functionality with the derived class. While this would work, there are maintainability concerns for the derived class.

Sealed Classes

Sealed classes are classes that can't be derived from. To prevent other classes from inheriting from a class, make it a sealed class. There are a couple good reasons to create sealed classes, including optimization and security.

Sealing a class avoids the system overhead associated with virtual methods. (The "Polymorphism" section later in this chapter has in-depth discussion of virtual methods.) This allows the compiler to perform certain optimizations that are otherwise unavailable with normal classes.

Another good reason to seal a class is for security. Inheritance, by its very nature, dictates a certain amount of protected access to the internals of a potential base class. Sealing a class does away with the possibility of corruption by derived classes. A good example of a sealed class is the String class. The following example shows how to create a sealed class:

public sealed class CustomerStats
{
  string gender;
  decimal income;
  int numberOfVisits;

  public CustomerStats()
  {
  }
}

public class CustomerInfo : CustomerStats // error
{
}

public class Customer
{
  CustomerStats myStats; // okay
}

This example generates a compiler error. Since the CustomerStats class is sealed, it can't be inherited by the CustomerInfo class. The CustomerStats class was meant to be used as an encapsulated object in another class. This is shown by the declaration of a CustomerStats object in the Customer class.

Encapsulating Object Internals

Encapsulation is an object-oriented concept associated with hiding the internals of a class from the outside world. C# has several mechanisms for supporting encapsulation. Some, such as properties and indexers, are new concepts we haven't seen implemented in languages before. There are several reasons to take advantage of C#'s built-in mechanisms for managing encapsulation:

  • Good encapsulation reduces coupling. By using only those class members exposed, users can write code with less dependency on that class.

  • Internal implementation of a class can freely change. This reduces the possibility of breaking someone else's code.

  • A class has a much cleaner interface. Users only see those members that are exposed, which reduces the amount of understanding they need to use a class. It simplifies reuse.

Data Hiding

One of the most useful forms of encapsulation is data hiding. Most of the time, users shouldn't have access to the internal data of a class. Class data represents the state of an object. A class normally has full control of its own state to guarantee its consistent behavior. Anytime access to data is opened, the potential of someone else wreaking havoc with the operation of that class increases.

There are times when it's logical and necessary to expose class data—especially if it's necessary to expose constants, enumerations, and read-only fields. Perhaps a design goal is to increase the efficiency of data access for a field that's accessed frequently. The decisions made depend on the requirements. However, give serious consideration to proper encapsulation of class information.

Modifiers Supporting Encapsulation

Manage class encapsulation with appropriate use of C# access modifiers, which specify who can access class members. They also control the method of access:

  • Private access is the most restrictive. This allows members, only within a class, to access another member marked as private. Anyone outside the class cannot access this member. They won't even know it's there without source code or documentation telling them otherwise. Private access is useful because it allows modification of a private member implementation without anyone knowing.

  • Protected access is a little less restrictive than private. Users may know the member is in a class, but they can't access protected members directly. The only way to use a protected member is through inheritance. A derived class has full access to protected base class members. This is regardless of the depth of the hierarchy. The protected member need not be in the derived class's immediate base class. Protected access is good for optimization when a derived class needs frequent access to base class information.

  • Internal access is for use only in the program or project where the data resides. If data only has particular relevancy in the context of a single program, this access is useful. This type of modifier would be used for in-house projects where a given class member was used by other teams on the same project. Other programs or user code would have no idea that this internal class member existed.

  • Protected Internal is a combination of protected and internal modifiers. It's a little bit more open than straight internal, allowing all members of a program to access the member. Additionally, derived classes of base classes in a program with protected internal members can access those members if they are either other program members or external user code. This access is useful for third-party libraries where users need access to protected members, with the added convenience that in-house developers would have free utilization of that class member without restriction.

  • Public access is the least restrictive of all. It lets anyone and everyone have access to class members without restriction. Public access is necessary to publish the interface of a class. It is through these members that communication with a class is accomplished. Great care should be taken to ensure that only those members contributing to effective use of an interface to a class are made public.

Other Encapsulation Strategies

The purpose of properties and indexers is to encapsulate the details of a class and provide a public interface to users of the class. See Chapter 7, "Working with Classes," for a detailed description of properties and indexers. Since one of their purposes is encapsulation, it's wise to use them as much as practical.

Relationship of Encapsulation to Inheritance

Encapsulation implies containment, where one object is inside of another. This is the "has a" relationship. An object inside another object is a field of its containing object.

When speaking of inheritance, it's useful to think of the "is a" relationship, where a class is a part of the classification hierarchy associated with its parent class.

Inheritance and containment are two different concepts, but one can be used improperly in place of the other. This text has repeatedly spoken of the "natural" inheritance hierarchy that is implemented between objects. Studies have shown inheritance is sometimes used where it doesn't necessarily make sense. For a good discussion, see C++ Programming Style, Tom Cargill, 1992, Addison-Wesley. Inheritance is good when applied naturally and is a good fit for the problem.

An alternative to inheritance is containment. By encapsulating one object within another, a class can control what behavior is used by derived classes. If need be, it can provide access to each member of the contained object through its own methods. In contrast, all class members in a base class, accessible to a derived class, are also accessible to further derivation. The efforts required to restrict base class access through a derived class would be tedious and error prone. Containment helps encapsulate the contained object's members.

Another factor to consider is that C# has only single inheritance. This means it can inherit functionality only from a single base class. Therefore, if a class already inherits from a base class, containment is the only way to reuse pre-canned functionality.


For C++ Programmers

C++ has multiple inheritance, whereas C# allows only single inheritance.


Polymorphism

Earlier sections of this chapter covered abstract classes, including the ultimate abstract class, object. It showed how to implement overrides of virtual classes in the object class. This section goes further by explaining how virtual classes are overridden, why, and what good it is. This capability enables an object-oriented programming concept known as polymorphism.

Implementing Polymorphism

To begin, it's useful to get an appreciation of the problem polymorphism solves. The key factor is the ability to dynamically invoke methods in a class based on their type. Essentially, a program would have a group of objects, examine the type of each one, and execute the appropriate method. Here's an example:

using System;

public class WebSite
{
  public string SiteName;
  public string URL;
  public string Description;

  public WebSite()
  {
  }

  public WebSite( string strSiteName, string strURL, string strDescription )
  {
    SiteName  = strSiteName;
    URL     = strURL;
    Description = strDescription;
  }

  public override string ToString()
  {
    return SiteName + ", " +
        URL   + ", " +
        Description;
  }
}

abstract public class Contact
{
  public virtual string UpdateNotify()
  {
    return "Web Site Change Notification";
  }
}

public class Customer : Contact
{
  public new string UpdateNotify()
  {
    return @"
This is to let you know your
favorite site, Financial Times,
has been updated with new links";
  }
}

public class SiteOwner : Contact
{
  WebSite mySite;

  public SiteOwner(string aName, WebSite aSite)
  {
    mySite = new WebSite(aSite.SiteName,
               aSite.URL,
               aSite.Description);
  }

  public new string UpdateNotify()
  {
    return @"
This is to let you know your site, " + "\n" +
mySite.SiteName + @", has been added as
a link to Financial Times.";
  }
}

public class Test
{
  public static void Main()
  {
    WebSite leFin = new WebSite("Le Financier",
                  "http://www.LeFinancier.com",
                  "Fancy Financial Site");

    Contact[] Contacts = new Contact[2];

    Contacts[0] = new SiteOwner("Pierre Doe", leFin);
    Contacts[1] = new Customer();

    foreach (Contact poc in Contacts)
    {
      if (poc is SiteOwner)
      {
        Console.WriteLine("Message: {0}\n",
         ((SiteOwner)poc).UpdateNotify());
      }
      else
      {
        Console.WriteLine("Message: {0}\n",
         ((Customer)poc).UpdateNotify());
      }
    }
  }
}

In this example, the Main() method of the Test class creates an array of Contact objects. It puts a SiteOwner object and a Customer object in the array. Each of these classes has an UpdateNotify() method, and the point of this program is to call the UpdateNotify() method belonging to each object.

The foreach loop checks the type of each object with the is operator. Depending on the type, the poc object is cast to that type and used in the Console.WriteLine() method. Here's another technique that could be used in the preceding foreach loop:

foreach (Contact poc in Contacts)
{
  SiteOwner anOwner = poc as SiteOwner;

  if (anOwner != null)
  {
    Console.WriteLine("Message: {0}\n",
             anOwner.UpdateNotify());
  }
  else
  {
    Console.WriteLine("Message: {0}\n",
             ((Customer)poc).UpdateNotify());
  }
}

This example uses the as operator. The as operator does an assignment of one object to another object when the type on the right side of the as operator is the same as the object on its left. Otherwise, the as operator returns null. This is more efficient than the is operator because the is operator required a type check and an assignment in two separate steps. In that last code example, the if statement only needs to check whether the value is not null and to execute the SiteOwner class UpdateNotify() method when this condition is true. Otherwise, the UpdateNotify() method of the Customer class is executed. Although the cast is necessary for Customer objects, using as is still more efficient because half of the objects don't need a cast.


Tip

Use the as operator for greater efficiency when iterating through a list of objects requiring type checks and casting. Use the is operator when a single object is being type checked or when casting is not necessary.


The preceding examples accomplish the task of dynamically invoking object methods. However, there is a more efficient and elegant way to accomplish the same thing. This method is called polymorphism. Polymorphism is efficient because C# rather than explicit coding is managing this process. It's also more elegant because there is less code, which makes for a simpler implementation.

Polymorphism is the capability of a program to carry out dynamic operations by implementing methods of multiple derived classes through a common base class reference. Another definition of polymorphism is the ability to treat different objects the same way. This means that the runtime type of an object determines its behavior rather than the compile-time type of its reference. Chapter 6, "Object and Component Concepts," discussed polymorphic behavior at a simplified and abstract level. It may help to review Chapter 6 and visualize those concepts before proceeding.

It's sometimes necessary to manipulate a collection of objects with multiple object types. A common task is to iterate through these objects performing some type of similar operation. Since the object types are different, it usually isn't possible to perform the same operation on each one. However, it would be convenient to request the same type of operation with specialized behavior for each object type. This is accomplished through polymorphism in a very efficient manner.

Imagine a scenario where a Web site creates notifications to multiple contacts about updates. There are different types of Contacts that require different types of notifications, but they are all Contacts. This example makes the assumption that Contact is a well-defined and natural abstraction for this purpose.

There are two types of Contacts interested in Web site updates: Customer and SiteOwner. While both types of Contacts are interested in updates, the actual message generated to each will be different, because each of their particular interests is different. Poly- morphism is a useful tool to solve this problem. Take a look at the following example:

using System;

abstract public class Contact
{
  public virtual string UpdateNotify()
  {
    return "Web Site Change Notification";
  }
}

public class Customer : Contact
{
  public override string UpdateNotify()
  {
    return @"
This is to let you know your
favorite site, Financial Times,
has been updated with new links";
  }
}

public class SiteOwner : Contact
{
  string siteName;

  public SiteOwner(string sName)
  {
    siteName = sName;
  }

  public override string UpdateNotify()
  {
    return @"
This is to let you know your site, " + "\n" +
siteName + @", has been added as
a link to Financial Times.";
  }
}

This example shows three primary classes: Contact, Customer, and SiteOwner. Contact is the abstract base class for the other two, providing a virtual UpdateNotify() method. Both Customer and SiteOwner override the Contact class UpdateNotify() method.

Virtual methods are those base class methods that enable polymorphism to work. They use the virtual modifier to indicate that they can be overridden by derived classes. The difference between abstract methods and virtual methods is that virtual methods have implementations, and abstract methods don't. Abstract methods are implicitly virtual, and they must be overridden. Virtual methods don't have to be overridden.

The override keyword indicates that a derived class method can be invoked at runtime, instead of the virtual base class method. The key points are

  • The object reference is a base class type, declaring the virtual method.

  • The runtime object is of the derived type with the overriding method.

The following code snippet shows polymorphism at work:

public class Test
{
  public static void Main()
  {
    Contact[] Contacts = new Contact[2];

    Contacts[0] = new SiteOwner("Le Financier");
    Contacts[1] = new Customer();

    foreach (Contact poc in Contacts)
    {
      Console.WriteLine("Message: {0}\n",
               poc.UpdateNotify());
    }
  }
}

And here's the output:

Message:
This is to let you know your site,
Le Financier, has been added as
a link to Financial Times.

Message:
This is to let you know your
favorite site, Financial Times,
has been updated with new links

This example shows a simple implementation using polymorphism. The program declares the array Contacts (plural) of type Contact. This is the first key point, the fact that the Contacts array possesses base class references to a virtual method. Also, Contact is the compile-time type of each Contacts array object.

Next, the program assigns objects of type SiteOwner and Customer to the Contacts array elements. This is the second key point, the fact that the runtime type of the object is a derived class with an override on a base class virtual method.

At runtime, the foreach loop uses the UpdateNotify() method of each Contacts array object. Although the compile-time type of each object is Contact, the Contact class virtual UpdateNotify() method is not executed. Instead, the overridden UpdateNotify() method of each derived class is executed.

Hiding Again

Now let's look at some scenarios with polymorphism-related modifiers and versioning. Using an override modifier in a derived class where there is no corresponding virtual method in a base class yields an error as in the following example:

abstract public class Contact
{
  public virtual string UpdateNotify()
  {
    return "Web Site Change Notification";
  }
}

public class Customer : Contact
{
  public override string SendMail() {}// error

  public override string UpdateNotify(int number) {}// error
}

This code produces an error during compilation. This is because the SendMail() method is declared with an override modifier, and there is not a corresponding virtual method to be overridden.

The same error occurs with the UpdateNotify() method in the Customer class. However, the reason is somewhat different. The UpdateNotify() method in the Customer class has a parameter, but the UpdateNotify() method of the Contact class doesn't have any parameters. Since there is a signature mismatch, polymorphism can't occur, and compilation generates an error. Remember, a method's signature consists of its name, number of parameters, and type of each parameter.

A virtual modifier by itself presents no problem at all. It's normal to label a method with a virtual modifier to indicate its availability for polymorphism to potential derived classes. This way any future classes may inherit from the class and override its virtual method.

When a derived class adds a normal method, with no modifiers, with the same signature of a base class virtual method, it generates a compile-time warning. This is the same behavior as described earlier with hiding. If you compile the following code, it generates a compiler warning:

abstract public class Contact
{
  public virtual string UpdateNotify()
  {
    return "Web Site Change Notification";
  }
}

public class Customer : Contact
{
  public string UpdateNotify()
  {
    return @"
This is to let you know your
favorite site, Financial Times,
has been updated with new links";
  }
}

There are two ways to correct this example. One is to add an override modifier to the derived class method:

public override string UpdateNotify() {...}

The other way is to add the new modifier to the derived class method. This hides the base class virtual method. Since the derived class hides the base class virtual method, any further derivations from the original derived class are not able to see the original base class virtual method. Here's an example:

public new string UpdateNotify() {...}

Earlier, there was an example of the UpdateNotify() method where each derived class overrode the virtual UpdateNotify() method in the Contact class. Here's an example of what happens when a virtual method is not overridden:

using System;

abstract public class Contact
{
  public virtual string UpdateNotify()
  {
    return "Web Site Change Notification";
  }
}

public class Customer : Contact
{
  public new string UpdateNotify()
  {
    return @"
This is to let you know your
favorite site, Financial Times,
has been updated with new links";
  }
}

public class SiteOwner : Contact
{
  string siteName;

  public SiteOwner(string sName)
  {
    siteName = sName;
  }

  public override string UpdateNotify()
  {
    return @"
This is to let you know your site, " + "\n" +
siteName + @", has been added as
a link to Financial Times.";
  }
}

public class Test
{
  public static void Main()
  {
    Contact[] Contacts = new Contact[2];

    Contacts[0] = new SiteOwner("Le Financier");
    Contacts[1] = new Customer();

    foreach (Contact poc in Contacts)
    {
      Console.WriteLine("Message: {0}\n",
               poc.UpdateNotify());
    }
  }
}

And here's the output:

Message:
This is to let you know your site,
Le Financier, has been added as
a link to Financial Times.

Message: Web Site Change Notification

This example shows what happens when virtual methods are not overridden. The UpdateNotify() method of the Customer class has a new modifier but does not have an override modifier. When the foreach loop of the Main() method of the Test class executes, it operates on Contact references to objects of type Customer and SiteOwner.

Viewing the output, the UpdateNotify() method of the SiteOwner class executes first. Since it overrides the virtual UpdateNotify() method of the Contact class, its method is executed. Next, the UpdateNotify() method of the Contact class executes. This time the UpdateNotify() method of the Customer class isn't executed, because the Customer class does not override the virtual UpdateNotify() method of the Contact class. When the runtime type of an object does not override a method of a virtual base class, the virtual method in the base class executes.

Most-Derived Implementations

The most derived implementation of a method is the lowest class in a hierarchy, down to the current class, that holds an implementation of a virtual method. The examples presented thus far have a base class and a derived class. To determine the most derived implementation, see whether the current object being referred to has an overridden implementation of a virtual method. If so, it is the most derived implementation. Otherwise, check the immediate base class of the current class, continuing up the hierarchy until an overriding method is found or the original virtual method itself is found. When there is only a single virtual method with no overrides in derived classes, then that virtual method is the most derived implementation. Here's an example that helps demonstrate how this works:

using System;

abstract public class Contact
{
  public virtual string UpdateNotify()
  {
    return "Web Site Change Notification";
  }
}

public class Customer : Contact
{
  public new string UpdateNotify()
  {
    return @"
This is to let you know your
favorite site, Financial Times,
has been updated with new links";
  }
}

public class SiteOwner : Contact
{
  string siteName;

  public SiteOwner(string sName)
  {
    siteName = sName;
  }

  public override string UpdateNotify()
  {
    return @"
This is to let you know your site, " + "\n" +
siteName + @", has been added as
a link to Financial Times.";
  }
}

public class PayingSiteOwner : SiteOwner
{
  public PayingSiteOwner(string ownerName)
     : base(ownerName)
  {
    // Initializers
  }

  public new string UpdateNotify()
  {
    return @"
This is to let you know your bill
is coming due. We award early
payment with a 5% discount.";
  }
}


public class Test
{
  public static void Main()
  {
    Contact[] Contacts = new Contact[3];

    Contacts[0] = new SiteOwner("Le Financier");
    Contacts[1] = new Customer();
    Contacts[2] = new PayingSiteOwner("Rip Uoff");

    foreach (Contact poc in Contacts)
    {
      Console.WriteLine("Message: {0}\n",
               poc.UpdateNotify());
    }
  }
}

And here's the output:

Message:
This is to let you know your site,
Le Financier, has been added as
a link to Financial Times.

Message: Web Site Change Notification

Message:
This is to let you know your site,
Rip Uoff, has been added as
a link to Financial Times.

The PayingSiteOwner class inherits SiteOwner, which in turn inherits Contact. The PayingSiteOwner class has an UpdateNotify() method that hides inherited UpdateNotify() methods. The SiteOwner class has an UpdateNotify() method that overrides the virtual UpdateNotify() method in the Contact class.

In the Main() method of the Test class is the declaration of both the SiteOwner and PayingSiteOwner classes. They are assigned to a Contact class reference. When the foreach loop executes, it calls the UpdateNotify() methods of each object in the array. Looking at the output, there are three outputs from UpdateNotify() methods. The first is from the overriding method in SiteOwner. The second is from the Contact class, which isn't overridden by the derived Customer class. The third entry is also from the SiteOwner class.

The reason for the third entry is because the UpdateNotify() method of the SiteOwner class is the most derived implementation of the UpdateNotify() method. Although the runtime object of the third entry is of the PayingSiteOwner class type, its UpdateNotify() method does not override the parent class UpdateNotify() method.

Since the object reference is a Contact class type, it searches for the most derived implementation of the virtual UpdateNotify() method. The search begins with the PayingSiteOwner class, where it doesn't find an override. Next, the base class of PayingSiteOwner, SiteOwner, is searched. A valid override exists there, so that is the method that gets executed.

If the example code was changed to

public class PayingSiteOwner : SiteOwner
{
  public PayingSiteOwner(string ownerName)
     : base(ownerName)
  {
    // Initializers
  }

  public override string UpdateNotify()
  {
    return @"
This is to let you know your bill
is coming due. We award early
payment with a 5% discount.";
  }
}

The output would be

Message:
This is to let you know your site,
Le Financier, has been added as
a link to Financial Times.

Message: Web Site Change Notification

Message:
This is to let you know your bill
is coming due. We award early
payment with a 5% discount.

The modifier on the UpdateNotify() method of the PayingSiteOwner class was changed from new to override. This made the UpdateNotify() method of the PayingSiteOwner class the most derived implementation, resulting in it being executed as the third entry of the output.

Polymorphic Properties

C# permits polymorphism with property accessors. The same rules applied to methods also apply to properties. Here's an example.

using System;

public class SiteStats
{
  public int numberOfVisits = 0;
}

abstract public class Contact
{
  protected string name;

  public virtual string Name
  {
    get
    {
      return name;
    }
    set
    {
      name = value;
    }
  }
}

public class Customer : Contact
{
  SiteStats myStats = new SiteStats();

  public override string Name
  {
    get
    {
      myStats.numberOfVisits++;
      Console.WriteLine("Number of visits: {0}",
               myStats.numberOfVisits);

      return name;
    }
    set
    {
      base.Name = value;
      myStats.numberOfVisits = 0;
      Console.WriteLine("Name: {0}", Name);
    }
  }
}

public class Test
{
  public static void Main()
  {
    Contact myContact = new Customer();
    myContact.Name = "George";
  }
}

And here's the output:

Number of visits: 1
Name: George

In this example, the Contact class declares the Name property with a virtual modifier. The Customer class overrides each of the Name property accessors. The set accessor of the Customer class Name property calls the set accessor of the Contact class Name property by using the base keyword.

The reason the output reflects access to both the get and set accessors can be seen in the set accessor of the Customer class Name property. It uses the Name property as an argument to the Console.WriteLine() method call. This causes a get to be performed using that class Name property. The get does its own Console.WriteLine() method, which results in the first line of output. The Console.WriteLine() method of the set accessor executes, producing the second line in the output.


Warning

C# allows both the get and set accessors of a property to reference the same property. Beware of creating circularities where the get accessor causes the set accessor to be called and vice versa. This results in an endless loop.


Polymorphic Indexers

C# permits polymorphism with indexer accessors. The same rules applied to methods and properties also apply to indexers. Here's an example:

using System;
using System.Collections;

public class SiteList
{
  protected SortedList sites;

  public SiteList()
  {
    sites = new SortedList();
  }

  public int NextIndex
  {
    get {
      return sites.Count;
    }
  }

  public virtual string this[int index]
  {
    get
    {
      return (string) sites.GetByIndex(index);
    }
    set
    {
      sites[index] = value;
    }
  }
}

public class FinancialSiteList : SiteList
{
  public override string this[int index]
  {
    get
    {
      Console.WriteLine("FinancialSiteList Indexer Get");
      if (index > sites.Count)
        return (string)null;

      return base[index];
    }
    set
    {
      Console.WriteLine("FinancialSiteList Indexer Set");
      base[index] = value;
    }
  }
}

class SiteManager
{
  SiteList sites = new SiteList();

  public static void Main()
  {
    SiteManager mgr = new SiteManager();

    mgr.sites = new FinancialSiteList();

    mgr.sites[mgr.sites.NextIndex] = "Great Site!";

    Console.WriteLine("Site: {0}",
      mgr.sites[0].ToString());
  }
}

And here's the output:

FinancialSiteList Indexer Set
FinancialSiteList Indexer Get
Site: Great Site!

In this example, the SiteList class declares its indexer as virtual. The FinancialSiteList indexer overrides the indexer of its base class, SiteList. The FinancialSiteList indexer accessors call the SiteList indexer accessors by using the base keyword with the index value.

The Main() method of the SiteManager class creates an object of type FinancialSiteList and assigns it to the sites field of the mgr object. The sites field is a SiteList class type. Then it assigns a string to the sites object. Because the FinancialSiteList indexer accessors override the SiteList indexer, the FinancialSiteList indexer set accessor is executed.

Viewing the output shows that the Console.WriteLine() method in the FinancialSiteList set accessor executed first. After the string is assigned to sites, the Main() method of SiteManager executes a Console.WriteLine() call. Because of polymorphism, this calls the get accessor of the FinancialSiteList class, which prints out the second line of output. Finally, the last line is printed from the Main() method of the SiteManager class.

Summary

In the first part of this chapter, I discussed inheritance. Issues associated with inheritance include base classes, abstract base classes, accessing base class members, hiding base class members, versioning, and sealed classes.

The next part covered encapsulation. Relevant encapsulation topics included data hiding, modifiers supporting encapsulation, encapsulation strategies using indexers and properties, and the relationship of encapsulation to inheritance.

Finally, the subject of polymorphism was explained. This section included strategies on how to implement polymorphism, the use of hiding in a polymorphic context, determining the most derived implementation of a virtual method, polymorphism with properties, and polymorphism with indexers.

This chapter touched upon the ability of classes to have multiple members with the same name when it presented constructor overloading. This is not all that C# can do with overloading, and you'll see why in the next chapter, "Overloading Class Members and Operators."