Chapter 5. Inheritance and Polymorphism

The preceding chapter demonstrated how to create new types by declaring classes. This chapter explores the relationship between objects in the real world and how to model these relationships in your code. This chapter focuses on specialization, which is implemented in C# through inheritance. This chapter also explains how instances of more specialized types can be treated as though they were instances of more general types, a process known as polymorphism. This chapter ends with a consideration of sealed classes, which can’t be specialized; abstract classes, which exist only to be specialized; and the root of all classes, the Object type.

Tip

VB 6 programmers take note: like VB.NET, C# provides full object-oriented technology, including inheritance, polymorphism, and encapsulation. These are relatively new topics for VB 6 programmers. You should study them carefully; they affect your class and application design.

Specialization and Generalization

Classes and their instances (objects) don’t exist in a vacuum, but rather in a network of interdependencies and relationships, just as we, as social animals, live in a world of relationships and categories.

The is-a relationship is one of specialization. When we say that a dog is-a mammal, we mean that the dog is a specialized kind of mammal. It has all the characteristics of any mammal (it bears live young, nurses with milk, has hair), but it specializes these characteristics to the familiar characteristics of Canis domesticus. A cat is also a mammal. As such, we expect it to share certain characteristics with the dog that are generalized in mammals, but to differ in those characteristics that are specialized in cats.

The specialization and generalization relationships are both reciprocal and hierarchical. They are reciprocal because specialization is the other side of the coin from generalization. Thus, dog and cat specialize mammal, and mammal generalizes from dog and cat.

These relationships are hierarchical because they create a relationship tree, with specialized types branching off from more generalized types. As you move up the hierarchy, you achieve greater generalization. You move up toward mammal to generalize that dogs and cats and horses all bear live young. As you move down the hierarchy, you specialize. Thus, the cat specializes mammal in having claws (a characteristic) and purring (a behavior).

Similarly, when you say that ListBox and Button are Controls you indicate that there are characteristics and behaviors of Controls that you expect to find in both of these types, as illustrated in Figure 5-1. In other words, Control generalizes the shared characteristics of both ListBox and Button, while each specializes its own particular characteristics and behaviors.

An is-a relationship
Figure 5-1. An is-a relationship

When developing an application, it is common to note that two classes share functionality, and then to factor out these commonalities into a shared base class. This provides you with easier-to-maintain code and greater reuse of common code. For example, suppose you started out creating a series of objects as illustrated in Figure 5-2.

Deriving from Control
Figure 5-2. Deriving from Control

After working with RadioButtons, CheckBoxes, and Command buttons for a while, you realize that they share certain characteristics and behaviors that are more specialized than Control, but more general than any of the three. You might factor these common traits and behaviors into a common base class, Button, and rearrange your inheritance hierarchy as shown in Figure 5-3. This is an example of how generalization is used in object-oriented development.

A more factored hierarchy
Figure 5-3. A more factored hierarchy

This UML diagram depicts the relationship between the factored classes and shows that both ListBox and Button derive from Control, and that Button is in turn specialized into CheckBox and Command. Finally, RadioButton derives from CheckBox. You can thus say that RadioButton is a CheckBox, which in turn is a Button, and that Buttons are Controls.

This is not the only, or even necessarily the best, organization for these objects, but it is a reasonable starting point for understanding how these types (classes) relate to one another.

Tip

Actually, although this might reflect how some widget hierarchies are organized, I’m very skeptical of any system in which the model doesn’t reflect how we perceive reality. When I find myself saying that a RadioButton is a CheckBox, I have to think long and hard about whether that makes sense. I suppose a RadioButton is a kind of checkbox. It is a checkbox that supports the idiom of mutually exclusive choices. With that said, it is a bit of a stretch, and might be a sign of a shaky design.

Microsoft offers a better design in Windows Presentation Foundation, in which ToggleButton serves as a base class for both CheckBox and RadioButton. The ButtonBase class then serves as the common base for Button and ToggleButton, thereby eliminating the artificial (and frankly bizarre) inheritance of RadioButton deriving from CheckBox.

Inheritance

In C#, the specialization relationship is typically implemented using inheritance. This is not the only way to implement specialization, but it is the most common and most natural way to implement this relationship.

Saying that ListBox inherits from (or derives from) Control indicates that it specializes Control. Control is referred to as the base class, and ListBox is referred to as the derived class. That is, ListBox derives its characteristics and behaviors from Control, and then specializes to its own particular needs.

Implementing Inheritance

In C#, you create a derived class by adding a colon after the name of the derived class, followed by the name of the base class:

public class ListBox : Control

This code declares a new class, ListBox, which derives from Control. You can read the colon as “derives from.”

Tip

C++ programmers take note: C# has no private or protected inheritance, and implements multiple inheritance only for interfaces, not for multiple base types. After eight years of C++ and now eight years of C#, I can honestly say that I see no disadvantage to this limitation.

The derived class inherits all the members of the base class, both member variables and methods.

Polymorphism

There are two powerful aspects to inheritance. One is code reuse. When you create a ListBox class, you’re able to reuse some of the logic in the base (Control) class.

What is arguably more powerful, however, is the second aspect of inheritance: polymorphism. Poly means “many” and morph means “form.” Thus, polymorphism refers to being able to use many forms of a type without regard to the details.

When the phone company sends your phone a ring signal, it doesn’t know what type of phone is on the other end of the line. You might have an old-fashioned Western Electric phone that energizes a motor to ring a bell, or you might have an electronic phone that plays digital music.

As far as the phone company is concerned, it knows only about the “base type” Phone and expects that any “instance” of this type knows how to ring. When the phone company tells your phone to ring, it simply expects the phone to “do the right thing.” Thus, the phone company treats your phone polymorphically.

Creating Polymorphic Types

Because a ListBox is-a Control and a Button is-a Control, we expect to be able to use either of these types in situations that call for a Control. For example, a form might want to keep a collection of all the instances of Control it manages so that when the form is opened, it can tell each of its Controls to draw itself. For this operation, the form doesn’t want to know which elements are listboxes and which are buttons; it just wants to tick through its collection and tell each to “draw.” In short, the form wants to treat all its Control objects polymorphically.

Creating Polymorphic Methods

To create a method that supports polymorphism, you need only mark it as virtual in its base class. For example, to indicate that the method DrawWindow( ) of class Control in Example 5-1 is polymorphic, simply add the keyword virtual to its declaration as follows:

public virtual void DrawWindow(  )

Now, each derived class is free to implement its own version of DrawWindow( ). To do so, simply override the base class virtual method by using the keyword override in the derived class method definition, and then add the new code for that overridden method.

In the following excerpt from Example 5-1 (which appears later in this section), ListBox derives from Control and implements its own version of DrawWindow( ):

publicoverride void DrawWindow(  )
{
  base.DrawWindow(  ); // invoke the base method
  Console.WriteLine ("Writing string to the listbox: {0}",
  listBoxContents);
}

The keyword override tells the compiler that this class has intentionally overridden how DrawWindow( ) works. Similarly, you’ll override this method in another class, Button, also derived from Control.

In the body of Example 5-1, you’ll first create three objects: a Control, a ListBox, and a Button. You’ll then call DrawWindow( ) on each:

Control win = new Control(1,2);
ListBox lb = new ListBox(3,4,"Stand alone list box");
Button b = new Button(5,6);
win.DrawWindow(  );
lb.DrawWindow(  );
b.DrawWindow(  );

This works much as you might expect. The correct DrawWindow( ) object is called for each. So far, nothing polymorphic has been done. The real magic starts when you create an array of Control objects. (Arrays are simple collections, covered in Chapter 9.) Because a ListBox is-a Control, you are free to place a ListBox into a Control array. You can also place a Button into an array of Control objects because a Button is also a Control:

Control[] winArray = new Control[3]; // declare an array of 3 Controls
winArray[0] = new Control(1,2);
winArray[1] = new ListBox(3,4,"List box in array");
winArray[2] = new Button(5,6);

What happens when you call DrawWindow( ) on each object?

for (int i = 0;i < 3; i++)
{
  winArray[i].DrawWindow(  );
}

All the compiler knows is that it has three Control objects, and that you’ve called DrawWindow( ) on each. If you had not marked DrawWindow as virtual, Control’s DrawWindow( ) method would be called three times. However, because you did mark DrawWindow( ) as virtual, and because the derived classes override that method, when you call DrawWindow( ) on the array, the compiler determines the runtime type of the actual objects (a Control, a ListBox, and a Button), and calls the right method on each. This is the essence of polymorphism. Example 5-1 shows the complete code for this example.

Example 5-1. Using virtual methods
using System;


namespace Using_virtual_methods
{
    public class Control
    {
        // these members are protected and thus visible
        // to derived class methods. We'll examine this
        // later in the chapter
// and then assign/refer to these as this.Top, this.Left in the rest of the code
        protected int Top { get; set; }
        protected int Left { get; set; }
        // constructor takes two integers to
        // fix location on the console
        public Control(int top, int left)
        {
            this.top = top;
            this.left = left;
        }


        // simulates drawing the window
        public virtual void DrawWindow(  )
        {
            Console.WriteLine("Control: drawing Control at {0}, {1}",
            top, left);
        }
    }


    // ListBox derives from Control
    public class ListBox : Control
    {
        private string listBoxContents; // new member variable


        // constructor adds a parameter
        public ListBox(
        int top,
        int left,
        string contents) :
            base(top, left) // call base constructor
        {


            listBoxContents = contents;
        }// an overridden version (note keyword) because in the
        // derived method we change the behavior
        public override void DrawWindow(  )
        {
            base.DrawWindow(  ); // invoke the base method
            Console.WriteLine("Writing string to the listbox: {0}",
            listBoxContents);
        }
    }


    public class Button : Control
    {
        public Button(
        int top,
        int left) :
            base(top, left)
        {
        }




        // an overridden version (note keyword) because in the
        // derived method we change the behavior
        public override void DrawWindow(  )
        {
            Console.WriteLine("Drawing a button at {0}, {1}
",
            top, left);
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Control win = new Control(1, 2);
            ListBox lb = new ListBox(3, 4, "Stand alone list box");
            Button b = new Button(5, 6);
            win.DrawWindow(  );
            lb.DrawWindow(  );
            b.DrawWindow(  );


            Control[] winArray = new Control[3];
            winArray[0] = new Control(1, 2);
            winArray[1] = new ListBox(3, 4, "List box in array");
            winArray[2] = new Button(5, 6);


            for (int i = 0; i < 3; i++)
            {
                winArray[i].DrawWindow(  );
            }
        }
    }
}




Output:
Control: drawing Control at 1, 2
Control: drawing Control at 3, 4
Writing string to the listbox: Stand alone list box
Drawing a button at 5, 6


Control: drawing Control at 1, 2
Control: drawing Control at 3, 4
Writing string to the listbox: List box in array
Drawing a button at 5, 6

Note that throughout this example we’ve marked the new overridden methods with the keyword override:

public override void DrawWindow( )

The compiler now knows to use the overridden method when treating these objects polymorphically. The compiler is responsible for tracking the real type of the object and for ensuring that it is ListBox.DrawWindow( ) that is called when the Control reference really points to a ListBox object.

Tip

C++ programmers take note: you must explicitly mark the declaration of any method that overrides a virtual method with the keyword override.

Calling Base Class Constructors

In Example 5-1, the new class ListBox derives from Control and has its own constructor, which takes three parameters. The ListBox constructor invokes the constructor of its parent (Control) by placing a colon (:) after the parameter list and then invoking the base class with the keyword base:

public ListBox(
  int theTop,
  int theLeft,
  string theContents):base(theTop, theLeft) // call base constructor

Because classes can’t inherit constructors, a derived class must implement its own constructor and can only make use of the constructor of its base class by calling it explicitly.

If the base class has an accessible (e.g., public) default constructor, the derived constructor is not required to invoke the base constructor explicitly; instead, the default constructor is called implicitly. However, if the base class doesn’t have a default constructor, every derived constructor must explicitly invoke one of the base class constructors using the base keyword.

Tip

As discussed in Chapter 4, if you don’t declare a constructor of any kind, the compiler will create a default constructor for you. Whether you write it or you use the one provided “by default” by the compiler, a default constructor is one that takes no parameters. Note, however, that once you do create a constructor of any kind (with or without parameters), the compiler doesn’t create a default constructor for you.

Controlling Access

The visibility of a class and its members can be restricted through the use of access modifiers, such as public, private, protected, internal, and protected internal. (See Chapter 4 for a discussion of access modifiers.)

As you’ve seen, public allows a member to be accessed by the member methods of other classes, and private indicates that the member is visible only to member methods of its own class. The protected keyword extends visibility to methods of derived classes, whereas internal extends visibility to methods of any class in the same assembly.

The internal protected keyword pair allows access to members of the same assembly (internal) or derived classes (protected). You can think of this designation as internal or protected.

Classes as well as their members can be designated with any of these accessibility levels. If a class member has an access designation that is different from that of the class, the more restricted access applies. Thus, if you define a class, myClass, as follows:

public class myClass
{
  // ...
  protected int myValue;
}

the accessibility for myValue is protected even though the class itself is public. A public class is one that is visible to any other class that wishes to interact with it. Often, classes are created that exist only to help other classes in an assembly, and these classes might be marked internal rather than public (the default for classes is internal, but it is good programming practice to make the accessibility explicit).

Versioning with the new and override Keywords

In C#, the programmer’s decision to override a virtual method is made explicit with the override keyword. This helps you to release new versions of your code; changes to the base class will not break existing code in the derived classes. The requirement to use the keyword override helps prevent that problem.

Here’s how: assume for a moment that the Control base class of the preceding example was written by Company A. Suppose also that the ListBox and RadioButton classes were written by programmers from Company B using a purchased copy of the Company A Control class as a base. The programmers in Company B have little or no control over the design of the Control class, including future changes that Company A might choose to make.

Now, suppose that one of the programmers for Company B decides to add a Sort( ) method to ListBox:

public class ListBox : Control
{
  public virtual void Sort(  ) {...}
}

This presents no problems until Company A, the author of Control, releases version 2 of its Control class, and it turns out that the programmers in Company A have also added a Sort( ) method to their public class Control:

public class Control
{
  // ...
  public virtual void Sort(  ) {...}
}

In other object-oriented languages (such as C++), the new virtual Sort( ) method in Control would now act as a base method for the virtual Sort( ) method in ListBox. The compiler would call the Sort( ) method in ListBox when you intend to call the Sort( ) in Control. In Java, if the Sort( ) in Control has a different return type, the class loader would consider the Sort( ) in ListBox to be an invalid override and would fail to load.

C# prevents this confusion. In C#, a virtual function is always considered to be the root of virtual dispatch; that is, once C# finds a virtual method, it looks no further up the inheritance hierarchy. If a new virtual Sort( ) function is introduced into Control, the runtime behavior of ListBox is unchanged.

When ListBox is compiled again, however, the compiler generates a warning:

...class1.cs(54,24): warning CS0114: 'ListBox.Sort(  )' hides
inherited member 'Control.Sort(  )'.
To make the current member override that implementation,
add the override keyword. Otherwise add the new keyword.

To remove the warning, the programmer must indicate what he intends. He can mark the ListBox Sort( ) method with new, to indicate that it is not an override of the virtual method in Control:

public class ListBox : Control
{
 public new virtual void Sort(  ) {...}

This action removes the warning. If, on the other hand, the programmer does want to override the method in Control, he need only use the override keyword to make that intention explicit:

public class ListBox : Control
{
 public override void Sort(  ) {...}

Warning

To avoid this warning, it might be tempting to add the keyword new to all your virtual methods. This is a bad idea. When new appears in the code, it ought to document the versioning of code. It points a potential client to the base class to see what you aren’t overriding. Using new scattershot undermines this documentation. Further, the warning exists to help identify a real issue.

Abstract Classes

Every subclass of Control should implement its own DrawWindow( ) method—but nothing requires that it do so. To require subclasses to implement a method of their base, you need to designate that method as abstract.

An abstract method has no implementation. It creates a method name and signature that must be implemented in all derived classes. Furthermore, making one or more methods of any class abstract has the side effect of making the class abstract.

Abstract classes establish a base for derived classes, but it is not legal to instantiate an object of an abstract class. Once you declare a method to be abstract, you prohibit the creation of any instances of that class.

Thus, if you were to designate DrawWindow( ) as abstract in the Control class, you could derive from Control, but you could not create any Control objects. Each derived class would have to implement DrawWindow( ). If the derived class failed to implement the abstract method, that class would also be abstract, and again no instances would be possible.

Designating a method as abstract is accomplished by placing the keyword abstract at the beginning of the method definition, as follows:

abstract public void DrawWindow(  );

(Because the method can have no implementation, there are no braces; only a semicolon.)

If one or more methods are abstract, the class definition must also be marked abstract, as in the following:

abstract public class Control

Example 5-2 illustrates the creation of an abstract Control class and an abstract DrawWindow( ) method.

Example 5-2. Using an abstract method and class
using System;


namespace abstract_method_and_class
{
    abstract public class Control
    {
        protected int top;
        protected int left;


        // constructor takes two integers to
        // fix location on the console
        protected Control(int top, int left)
        {
            this.top = top;
            this.left = left;
        }


        // simulates drawing the window
        // notice: no implementation
        abstract public void DrawWindow(  );


    }


    // ListBox derives from Control
    public class ListBox : Control
    {
        private string listBoxContents; // new member variable


        // constructor adds a parameter
        public ListBox(
        int top,
        int left,
        string contents) :
            base(top, left) // call base constructor
        {


            listBoxContents = contents;
        }


        // an overridden version implementing the
        // abstract methodpublic override void DrawWindow(  )
        {
            Console.WriteLine("Writing string to the listbox: {0}",
            listBoxContents);
        }


    }


    public class Button : Control
    {
        public Button(
        int top,
        int left) :
            base(top, left)
        {
        }


        // implement the abstract method
        public override void DrawWindow(  )
        {
            Console.WriteLine("Drawing a button at {0}, {1}
",
            top, left);
        }


    }




    class Program
    {
        static void Main(string[] args)
        {
            Control[] winArray = new Control[3];
            winArray[0] = new ListBox(1, 2, "First List Box");
            winArray[1] = new ListBox(3, 4, "Second List Box");
            winArray[2] = new Button(5, 6);


            for (int i = 0; i < 3; i++)
            {
                winArray[i].DrawWindow(  );
            }


        }
    }
}

In Example 5-2, the Control class has been declared abstract and therefore can’t be instantiated. If you replace the first array member:

winArray[0] = new ListBox(1,2,"First List Box");

with this code:

winArray[0] = new Control(1,2);

the program generates the following error:

Cannot create an instance of the abstract class or interface
'abstractmethods.Control'

You can instantiate the ListBox and Button objects because these classes override the abstract method, thus making the classes concrete (i.e., not abstract).

Limitations of Abstract

Although designating DrawWindow( ) as abstract does force all the derived classes to implement the method, this is a very limited solution to the problem. If we derive a class from ListBox (e.g., DropDownListBox), nothing forces that derived class to implement its own DrawWindow( ) method.

Tip

C++ programmers take note: in C#, it is not possible for Control.DrawWindow( ) to provide an implementation, so you can’t take advantage of the common DrawWindow( ) routines that might otherwise be shared by the derived classes.

Finally, abstract classes should not just be an implementation trick; they should represent the idea of an abstraction that establishes a “contract” for all derived classes. In other words, abstract classes describe the public methods of the classes that will implement the abstraction.

The idea of an abstract Control class ought to lay out the common characteristics and behaviors of all Controls, even if you never intend to instantiate the abstraction Control itself.

The idea of an abstract class is implied in the word abstract. It serves to implement the abstraction of “control” that will be manifest in the various concrete instances of Control, such as browser window, frame, button, listbox, or drop-down menu. The abstract class establishes what a Control is, even though you never intend to create a control per se. An alternative to using abstract is to define an interface, as described in Chapter 8.

Sealed Class

The obverse side of the design coin from abstract is sealed. Although an abstract class is intended to be derived from and to provide a template for its subclasses to follow, a sealed class doesn’t allow classes to derive from it at all. Placed before the class declaration, the sealed keyword precludes derivation. Classes are most often marked sealed to prevent accidental inheritance.

Tip

Java programmers take note: a sealed class in C# is the equivalent of a final class in Java.

If you change the declaration of Control in Example 5-2 from abstract to sealed (eliminating the abstract keyword from the DrawWindow( ) declaration as well), the program will fail to compile. If you try to build this project, the compiler will return the following error message:

'ListBox' cannot inherit from sealed class 'Control'

among many other complaints (such as that you can’t create a new protected member in a sealed class).

The Root of All Types: Object

All C# classes, of any type, are treated as though they ultimately derive from System.Object. Interestingly, this includes value types.

A base class is the immediate “parent” of a derived class. A derived class can be the base to further derived classes, creating an inheritance “tree” or hierarchy. A root class is the topmost class in an inheritance hierarchy.

In C#, the root class is Object. The nomenclature is a bit confusing until you imagine an upside-down tree, with the root on top, and the derived classes below. Thus, the base class is considered to be “above” the derived class.

Tip

C++ programmers take note: C# uses single inheritance with a monolithic class hierarchy: every class inherits from a base class of Object, and multiple inheritance is not possible. However, C# interfaces provide many of the benefits of multiple inheritance. (See Chapter 8 for more information.)

Object provides a number of virtual methods that subclasses can and do override. These include Equals( ) to determine whether two objects are the same; GetType( ), which returns the type of the object (discussed in Chapter 8); and ToString( ), which returns a string to represent the current object (discussed in Chapter 10). Table 5-1 summarizes the methods of Object.

Table 5-1. The methods of Object

Method

What it does

Equals( )

Evaluates whether two objects are equivalent

GetHashCode( )

Allows objects to provide their own hash function for use in collections (see Chapter 9)

GetType( )

Provides access to the type object

ToString( )

Provides a string representation of the object

Finalize( )

Cleans up unmanaged resources; implemented by a destructor (see Chapter 4)

MemberwiseClone( )

Creates copies of the object; should never be implemented by your type

ReferenceEquals( )

Evaluates whether two objects refer to the same instance

Example 5-3 illustrates the use of the ToString( ) method inherited from Object, as well as the fact that primitive datatypes such as int can be treated as though they inherit from Object. Note that the DisplayValue method expects an object, but works perfectly fine if you pass in an integer.

Example 5-3. Inheriting from Object
using System;


namespace Inheriting_From_Object
{
    public class SomeClass
    {
        private int val;


        public SomeClass(int someVal)
        {
            val = someVal;
        }


        public override string ToString(  )
        {
            return val.ToString(  );
        }
    }


    class Program
    {
        static void DisplayValue( object o )
        {
            Console.WriteLine(
                "The value of the object passed in is {0}", o;
        }




        static void Main(string[] args)
        {


            int i = 5;
            Console.WriteLine("The value of i is: {0}", i.ToString(  ));
            DisplayValue(i);


            SomeClass s = new SomeClass(7);
            Console.WriteLine("The value of s is {0}", s.ToString(  ));
            DisplayValue(s);
        }
    }
}




Output:
The value of i is: 5
The value of the object passed in is 5
The value of s is 7
The value of the object passed in is 7

The documentation for Object.ToString( ) reveals its signature:

public virtual string ToString(  );

It is a public virtual method that returns a string and that takes no parameters. All the built-in types, such as int, derive from Object and so can invoke Object’s methods. The Write and WriteLine methods of Console will automagically invoke the ToString( ) method on any object provided.

Example 5-3 overrides the virtual function for SomeClass, which is the usual case, so that the class’s ToString( ) method will return a meaningful value. If you comment out the overridden function, the base method will be invoked, which will change the output to:

The value of s is SomeClass

Thus, the default behavior is to return a string with the name of the class itself.

Classes don’t need to explicitly declare that they derive from Object; the inheritance is implicit.

Nesting Classes

Classes have members, and it is entirely possible for the member of a class to be another user-defined type. Thus, a Button class might have a member of type Location, and a Location class might contain members of type Point. Finally, Point might contain members of type int.

At times, the contained class might exist only to serve the outer class, and there might be no reason for it to be otherwise visible. (In short, the contained class acts as a helper class.) You can define the helper class within the definition of the outer class. The contained, inner class is called a nested class, and the class that contains it is called, simply, the outer class.

Nested classes have the advantage of access to all the members of the outer class. A method of a nested class can access private members of the outer class.

Tip

In addition, the nested class can be hidden from all other classes—that is, it can be private to the outer class.

Finally, a nested class that is public is accessed within the scope of the outer class. If Outer is the outer class, and Nested is the (public) inner class, refer to Nested as Outer.Nested, with the outer class acting (more or less) as a namespace or scope.

Tip

Java programmers take note: nested classes are roughly equivalent to static inner classes; there is no C# equivalent to Java’s nonstatic inner classes.

Example 5-4 features a nested class of Fraction named FractionArtist. The job of FractionArtist is to render the fraction on the console. In this example, the rendering is handled by a pair of simple WriteLine( ) statements.

Example 5-4. Using a nested class
using System;


namespace Nested_Class
{
    public class Fraction
    {
        private int numerator;
        private int denominator;


        public Fraction(int numerator, int denominator)
        {
            this.numerator = numerator;
            this.denominator = denominator;
        }


        public override string ToString(  )
        {
            return String.Format("{0}{1}",
                numerator, denominator);
        }internal class FractionArtist
        {
            public void Draw(Fraction f)
            {
                Console.WriteLine("Drawing the numerator: {0}",
                    f.numerator);
                Console.WriteLine("Drawing the denominator: {0}",
                    f.denominator);
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Fraction f1 = new Fraction(3, 4);
            Console.WriteLine("f1: {0}", f1.ToString(  ));


            Fraction.FractionArtist fa = new Fraction.FractionArtist(  );
            fa.Draw(f1);


        }
    }
}

The nested class is shown in bold. The FractionArtist class provides only a single member, the Draw( ) method. What is particularly interesting is that Draw( ) has access to the private data members f.numerator and f.denominator, to which it wouldn’t have had access if it weren’t a nested class.

Notice in Main( ) that to declare an instance of this nested class, you must specify the type name of the outer class:

Fraction.FractionArtist fa = new Fraction.FractionArtist(  );

FractionArtist is scoped to be within the Fraction class.

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

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