Classes

I l @ ve RuBoard

A class represents the encapsulation of data and methods that act on that data. In C#, classes are considered reference types and, as such, instances of classes are allocated on the heap and managed by the GC. When an instance of a class is created, memory is allocated on the heap and the object is referenced counted. When the reference count for the object reaches zero, the GC will reclaim the memory area being used by the object and return that memory to the available memory pool.

Classes can contain fields, methods, events, properties, and nested classes. Classes also have the ability to inherit from another class and implement multiple interfaces.

Like struct s, the default protection level for class members is private. Classes can declare members to be public, protected, private, internal, or protected internal.

Declaring a class consists of specifying the following:

 [attributes] [access modifier] class class-name [: [base-class], [interface]*] {     body } 

The square brackets indicate optional specifiers and are not required to declare a class. Listing 2.1.23 presents a simple class representation for a Dog type.

Listing 2.1.23 A Simple Class
 1: using System;  2:  3: public class Dog {  4:  5:    //Fields  6:    private string Name;  7:  8:    //Constructor  9:    public Dog( string name ) { 10:     Name = name; 11:    } 12: 13:    //Methods 14:    public void Speak( ) { 15:        Console.WriteLine( "Hello. My name is {0} ", Name ); 16:    } 17: } 18: 19: public class Simple { 20: 21:     public static void Main( ) { 22: 23:         Dog spot = new Dog( "spot" ); 24:         spot.Speak( ); 25:     } 26: } 

The Dog class in Listing 2.1.23 demonstrates the basics of defining and implementing a class in C#. The class contains a private field, a parameter-based constructor, and a single method. Unlike a struct , the new operator must be used to create an instance of the class. Line 23 shows the creating of a new instance of the Dog class and uses the parameter constructor to initialize the object.

As a brief note on access modifiers, Table 2.1.3 presents the access modifiers and how they affect class members.

Table 2.1.3. Member Access Modifiers
Access Modifier Definition
public Visible to all code
protected Visible to current class and derived classes
private Visible only to current class
internal Visible to current assembly only
protected internal Visible to current assembly or types derived from the class

Object

Every class in C# implicitly derives from the base class System.Object . Because all classes derive from a common base class, the ability to create generic collection classes becomes a trivial point. Every instance of a class can be treated as if it were a System.Object instance.

The System.Object class also provides a few basic services that other classes in the .NET framework use. For example, the Console.Write method will use the ToString method of a class to display the class to the console. Any C# class can thus override the behavior of ToString and provide a custom implementation specific to the class. Table 2.1.4 lists some of the basic Object methods.

Table 2.1.4. The System.Object Class
Method Purpose
Equals( Object ) Boolean comparison
Finalize Similar to a C++ destructor
ToString Convert class to string representation

The System.Object base class also provides additional methods for type information, reflection, and cloning. These topics are outside the scope of this conversation, but their exploration is well worth the time spent.

Methods

In OO terminology, a method represents an object message. Methods can be either instance or static in nature. An instance method requires an instance of the object and generally acts on the data members of the current object. Static methods do not require an instance of an object and, therefore, cannot access the data members of the current class.

Methods, like data members, can also have an access modifier applied to them. Public methods allow any user of the object to invoke that method; it is the public contract, so to speak. Protected methods are only to be used by the object itself or any object that derives from the object. Private methods can only be accessed by the class declaring the method. Derived classes cannot make use of any private methods found in the base class.

Listing 2.1.24 demonstrates the use of instance verses static methods.

Listing 2.1.24 Instance and Static Methods
 1: using System;  2:  3: public class MyMath {  4:  5:     //instance method  6:     public long Factorial( long l ) {  7:         return l <= 0 ? 1 : l * Factorial( l - 1 );  8:     }  9: 10:     //static method 11:     public static long SFactorial( long l ) { 12:         return l <= 0 ? 1 : l * SFactorial( l - 1 ); 13:     } 14: } 15: 16: public class Methods { 17: 18:     public static void Main ( ) { 19: 20:         //Use the static method 21:         Console.WriteLine("5 Factorial = {0} ", MyMath.SFactorial( 5 ) ); 22: 23:         //Use the instance method 24:         MyMath m = new MyMath( ); 25:         Console.WriteLine("5 Factorial = {0} ", m.Factorial( 5 ) ); 26:     } 27: } 

To access a static method, the method name needs to be qualified with the name of the class. Line 21 of Listing 2.1.24 invokes the SFactorial method of the MyMath class by making use of the dot operator. C# has unified member, method, and scooping access to the dot operator. In C++, it was necessary to use the pointer access operator, the dot operator or the scope resolution operator, depending on the situation. Not so in C#; only the dot operator is necessary to perform all access.

Instance methods require an object to invoke them. The Factorial method is such a method because the static modifier has not been applied to the method. Line 24 creates an instance of the MyMath class to invoke it. Notice that the same dot operator is used to access the instance method the same way that access to the static method is specified.

Parameter Passing

Depending on the language you are familiar with, there exists specific syntax for defining whether a parameter is passed by-value ”a copy of the parameter is placed on the call stack ”or by-reference ”an alias to the variable is placed on the call stack. In C and C++, you can pass a parameter by value, by reference, pass a pointer, and so on. Users of Visual Basic know that parameters are passed by reference by default and need to specify by value otherwise .

C# offers not only supports by-value and by-reference parameter passing, but it also allows for additional marshaling instructions, such as in and out. These modifiers are orthogonal to the in and out directives used in COM.

In C#, value types, such as primitive types and struct s, are passed by value unless otherwise specified. When a parameter is passed by value, a copy of the value type is created. The method receiving the parameter can make use of the copy and even modify its value. However, the parameter is not in any way related to the outside world. If a method modifies a value passed by value, the effects of that modification only exist within the scope of that method.

Reference types ”any class or interface ”are passed by reference and cannot be passed by value. To pass a value type by reference, the ref keyword must be used. When a parameter is passed by reference to a method, the method can modify the parameter, and the modifications will affect the actual parameter, thus producing a side effect. Listing 2.1.25 demonstrates parameter passing available in C#.

Listing 2.1.25 Parameter Passing
 1:   2: using System;   3:   4: //Create a value type   5: public struct Point {   6:   7:     public int x;   8:     public int y;   9: }  10:  11: //Create a Reference type  12: public class MyObject {  13:     public int i;  14: }  15:  16:  17:  18: public class Pass {  19:  20:     public static void Main( ) {  21:  22:  23:         //Create a primitive type and pass to the various methods  24:         int i = 100;  25:  26:         Console.WriteLine( "Value of i before PassByValue Method is {0} ", i );  27:         PassByValue( i );  28:         Console.WriteLine( "Value of i after PassByValue Method is {0} ", i );  29:  30:         Console.WriteLine("");  31:  32:         Console.WriteLine( "Value of i before PassByRef Method is {0} ", i );  33:         PassByRef( ref i );  34:         Console.WriteLine( "Value of i before PassByRef Method is {0} ", i );  35:  36:  37:         Console.WriteLine("");  38:  39:         //Create an the Point type  40:         Point p; p.x = 10; p.y = 15;  41:         Console.WriteLine( "Value of p before PassByValue is x={0} , y={1} ", p.x, graphics/ccc.gif p.y);  42:         PassByValue( p );  43:         Console.WriteLine( "Value of p after PassByValue is x={0} , y={1} ", p.x, graphics/ccc.gif p.y);  44:  45:         Console.WriteLine("");  46:  47:         Console.WriteLine( "Value of p before PassByRef is x={0} , y={1} ", p.x,p.y);  48:         PassByRef( ref p );  49:         Console.WriteLine(  "Value of p after PassByRef is x={0} , y={1} ", p.x,p.y);  50:  51:  52:         Console.WriteLine("");  53:  54:         //Create an object instance  55:         MyObject o = new MyObject( );  56:         o.i = 10;  57:  58:         Console.WriteLine( "Value of o.i before PassReferenceType is {0} ", o.i );  59:         PassReferenceType( o );  60:         Console.WriteLine( "Value of o.i after PassReferenceType is {0} ", o.i );  61:  62:  63:     }  64:  65:  66:  67:     public static void PassByValue( Point p )  {  68:         Console.WriteLine( "Entering public static void PassByvalue( Point p )" );  69:  70:         Console.WriteLine( "Value of Point.x = {0}  : Point.y = {1} ", p.x, p.y );  71:         p.x++; p.y++;  72:         Console.WriteLine( "New Value of Point.x = {0}  : Point.y = {1} ", p.x, p.y graphics/ccc.gif );  73:  74:         Console.WriteLine(  "Exiting public static void PassByvalue( Point p )" );  75:     }  76:  77:  78:     public static void PassByValue( int i ) {  79:         Console.WriteLine( "Entering public static void PassByValue( int i )" );  80:  81:         Console.WriteLine("Value of i = {0} ", i );  82:         i++;  83:         Console.WriteLine("New Value of i = {0} ", i );  84:  85:         Console.WriteLine( "Exiting public static void PassByValue( int i )" );  86:     }  87:  88:     public static void PassByRef( ref Point p ) {  89:         Console.WriteLine( "Entering public static void PassByRef( ref Point p )" );  90:  91:         Console.WriteLine( "Value of Point.x = {0}  : Point.y = {1} ", p.x, p.y );  92:         p.x++; p.y++;  93:         Console.WriteLine( "New Value of Point.x = {0}  : Point.y = {1} ", p.x, p.y graphics/ccc.gif );  94:  95:         Console.WriteLine( "Exiting public static void PassByRef( ref Point p )" );  96:     }  97:  98:     public static void PassByRef( ref int i ) {  99:         Console.WriteLine( "Entering public static void PassByRef( ref int i )" ); 100: 101:         Console.WriteLine("Value of i = {0} ", i ); 102:         i++; 103:         Console.WriteLine("New Value of i = {0} ", i ); 104: 105:         Console.WriteLine( "Exiting public static void PassByRef( ref int i )" ); 106:     } 107: 108:     public static void PassReferenceType( MyObject o ) { 109:         Console.WriteLine( "Entering public static void PassReferenceType( MyObject graphics/ccc.gif o )" ); 110: 111:         Console.WriteLine("Value of MyObject.i = {0} ", o.i); 112:         o.i++; 113:         Console.WriteLine("New Value of MyObject.i = {0} ", o.i); 114: 115:         Console.WriteLine( "Exiting public static void PassReferenceType( MyObject o graphics/ccc.gif )" ); 116:     } 117: } 

The parameter passing Listing 2.1.25 presents cases for passing primitive types, struct s, and reference types to methods by value and by reference.

Output of Listing 2.1.25

 Value of i before PassByValue Method is 100 Entering public static void PassByValue( int i ) Value of i = 100 New Value of i = 101 Exiting public static void PassByValue( int i ) Value of i after PassByValue Method is 100 Value of i before PassByRef Method is 100 Entering public static void PassByRef( ref int i ) Value of i = 100 New Value of i = 101 Exiting public static void PassByRef( ref int i ) Value of i before PassByRef Method is 101 Value of p before PassByValue is x=10, y=15 Entering public static void PassByvalue( Point p ) Value of Point.x = 10 : Point.y = 15 New Value of Point.x = 11 : Point.y = 16 Exiting public static void PassByvalue( Point p ) Value of p after PassByValue is x=10, y=15 Value of p before PassByRef is x=10, y=15 Entering public static void PassByRef( ref Point p ) Value of Point.x = 10 : Point.y = 15 New Value of Point.x = 11 : Point.y = 16 Exiting public static void PassByRef( ref Point p ) Value of p after PassByRef is x=11, y=16 Value of o.i before PassReferenceType is 10 Entering public static void PassReferenceType( MyObject o ) Value of MyObject.i = 10 New Value of MyObject.i = 11 Exiting public static void PassReferenceType( MyObject o ) Value of o.i after PassReferenceType is 11 

Properties

In the C++ and COM world, properties are nothing more than a simple semantic for assessor and setter methods. In COM, the methods would be put_ T and get_ T where T is the property name. Visual Basic programmers will be immediately familiar with the concept of properties, because there exists a parallel among the entities.

C# allows for properties to be either read only, write only, or read/write, although a write-only property doesn't really have much use.

The property construct has the following syntax:

 access-modifier return-type PropertyName {     [ get { statement; }  ]     [ set { statement; }  ] } 

Properties provide a simple syntax for accessing elements within a class while still allowing for a level of abstraction as to the actual property implementation. Listing 2.1.26 revisits Newton's method for square roots and implements two properties: Value and Result .

Listing 2.1.26 Using Properties
 1: using System;  2:  3:  4: public class Newton {  5:  6:     private double m_dblValue;  7:     private double m_dblResult;  8:  9: 10:     public Newton( ) { 11:         m_dblValue  = 1.0; 12:         m_dblResult = 0.0; 13:         } 14: 15:     //Properties 16: 17:     //Value is set/get 18:     public double Value { 19:         set { 20:             if( value <= 0 ) { 21:                Console.WriteLine("Value must be greater than Zero"); 22:                return; 23:             } 24:             m_dblValue = value;    //the rhs is value 25:         } 26: 27:         get { return m_dblValue; } 28:     } 29: 30:     //The result is get only. 31:     public double Result { 32:         get { return m_dblResult; } 33:     } 34: 35:     //Find the Square Root of m_dblValue 36:     public void FindSqrt( ) { 37:         const double Epsilon = 1.0e-9; 38:         double         Guess   = 11; 39: 40:         m_dblResult = ((m_dblValue / Guess) + Guess) / 2; 41: 42:         while( Math.Abs( m_dblResult - Guess ) > Epsilon) { 43:             Guess  = m_dblResult; 44:             m_dblResult = ((m_dblValue / Guess) + Guess) / 2; 45:         } 46:     } 47: } 48: 49: 50: 51: 52: public class PropertyTest { 53: 54:     public static void Main( ) { 55: 56:         Newton n = new Newton( ); 57: 58:         //set the requested value 59:         n.Value = 100; 60: 61:         //Find the Sqrt of 100 62:         n.FindSqrt( ); 63: 64:         //display the result 65:         Console.WriteLine("The Sqrt of {0}  is {1} ", n.Value, n.Result ); 66:     } 67: } 

Returning to the Newton example, Listing 2.1.26 presents a class that implements Newton's method for approximating the square root of a number. The Newton class implements two properties, Value and Result . The Value property implements both the set and get methods. This allows the property to be read and written to. The Result property only implements the get method, effectively creating a read-only property.

The purpose of properties is to allow for a natural semantic for accessing data members but still allowing for a layer of abstraction. Within the implementation of the property accessor or setter, the developer is free to implement validation, conversion, and any other logic necessary. From the user's point of view, all the implementation detail has been abstracted away, and a clean semantic for accessing data elements is provided.

Operators

C# provides the facility for user-defined operators. Any struct or class can provide a specific implementation of a given operator, such as addition, subtraction, or casting from one type to another. The ability to create a new type and define operator semantics for it allows for the development of new value types as well as reference types.

In earlier discussions, the assignment operator was discussed and how it differs from value types and reference types. C# does not allow for the implementation of an assignment operator. C++ developers can scream now. This restriction is due to reference counting verses coping. Another consideration is the .NET platform is meant for languages to interoperate with each other in ways never before possible. To make a copy of a reference type, most reference types provide a Copy method. When developing classes, you should follow the same guidelines.

C# requires that all operators be static methods. Again, this makes sense to a language purist. Operators pertain to a type and not an instance, just one of the details that was not overlooked in the design of C#.

To gain an understanding of operator overloading, the Fraction class, Listing 2.1.27 demonstrates implementing the arithmetic operators + and -.

Listing 2.1.27 Operator Overloading
 1: //File        :part02_26.cs  2: //Author    :Richard L. Weeks  3: //Purpose    :Demonstrate operator overloading  4:  5: using System;  6:  7: public class Fraction {  8:  9:     //data members 10:     private int    m_numerator; 11:     private int    m_denominator; 12: 13:     //Properties 14:     public int Numerator { 15:         get { return m_numerator; } 16:         set { m_numerator = value; } 17:     } 18:     public int Denominator { 19:         get { return m_denominator; } 20:         set { m_denominator = value; } 21:     } 22: 23: 24:     //Constructors 25:     public Fraction( ) { m_numerator = 0; m_denominator = 0; } 26: 27:     public Fraction( int iNumerator, int iDenominator ) { 28:             m_numerator = iNumerator; 29:             m_denominator = iDenominator; 30:     } 31: 32:     //Arithmetic operators +,-,/,* 33: 34:     public static Fraction operator+(Fraction f1, Fraction f2) { 35:         Fraction Result = new Fraction( ); 36:         //In order to add fractions, the denominators need to be the same 37:         //the fastest way is to multiply them together and adjust the numerators 38:         if( f1.Denominator != f2.Denominator ) { 39:             Result.Denominator = f1.Denominator * f2.Denominator; 40:             Result.Numerator   = (f1.Numerator * f2.Denominator) + (f2.Numerator * graphics/ccc.gif f1.Denominator); 41:         }  else { 42:             Result.Denominator = f1.Denominator; 43:             Result.Numerator  = f1.Numerator + f2.Numerator; 44:         } 45:         return Result; 46:     } 47: 48: 49:     public static Fraction operator-(Fraction f1, Fraction f2) { 50:         Fraction Result = new Fraction( ); 51:         //In order to subtract fractions, the denominators need to be the same 52:         //the fastest way is to multiply them together and adjust the numerators 53:         if( f1.Denominator != f2.Denominator ) { 54:             Result.Denominator = f1.Denominator * f2.Denominator; 55:             Result.Numerator   = (f1.Numerator * f2.Denominator) - (f2.Numerator * graphics/ccc.gif f1.Denominator); 56:         }  else { 57:             Result.Denominator = f1.Denominator; 58:             Result.Numerator   = f1.Numerator - f2.Numerator; 59:         } 60:         return Result; 61:     } 62: 63: } 64: 65: 66: public class OperatorTest { 67: 68:     public static void Main( ) { 69: 70:         Fraction f1 = new Fraction( 1, 5 ); 71:         Fraction f2 = new Fraction( 2, 5 ); 72: 73:         //Add the Fractions 74:         Fraction f3 = f1 + f2; 75: 76:         //Display the result 77:         Console.WriteLine("f1 + f2 = {0} /{1} ", f3.Numerator, f3.Denominator ); 78: 79:         //Subtract f2 from f3 should get f1 80:         f3 = f3 - f2; 81:         Console.WriteLine("f3 - f2 = {0} /{1} ", f3.Numerator, f3.Denominator ); 82:     } 83: } 

The Fraction class presented in Listing 2.1.27 implements both the + and “ operators. The addition operator is implemented on line 34 and the subtraction operator on line 49. The general form for overloading an operator can be expressed as follows :

 public static  return-type  operator  T  (param p [,param p1]) 

where the return-type specifies the result of the operator, T is the actual operator to overload, and the number of parameters is dependant on the operator being overloaded.

In addition to standard arithmetic operators, C# provides the ability to overload relational operators and casting operators. Relational operators often come in pairs. For example, when overloading the equality operator ==, the inequality operator != must also be defined. Relational operators have the same semantic for overloading as the operators presented so far.

Casting operators have a slightly different semantic than regular operators. When implementing a casting operator, the decision of implicit or explicit must be made. Remember, an implicit cast does not require the type to be specified, whereas an explicit cast does.

 Fraction f = new Fraction( 1, 5 ); double d = f;            //implicit cast double dd = (double)f;        //explicit cast 

The decision about implicit verses explicit will need to be determined by the use case in mind. The syntax for overloading a casting operator is as follows:

 public static [implicitexplicit] operator  Return-Type  ( Type T ) 

Again, the Return-Type denotes to what the Type T is being cast or converted. Extending the previous operator example, the Fraction class in Listing 2.1.28 has been extended to implement an explicit cast to double and the relational operators == and !=. When compiling the code in Listing 2.1.28, the compiler will issue two warnings CS660 and CS661. The warning stems from the overloaded operators == and != and the requirement that any class overloading these operators must also provide an implementation of Object.Equals and Object.GetHashCode . For now you can dismiss the warnings. However when creating production code be sure to implement the necessary Equals and GetHashCode methods in order to satisfy the rule that says any two objects that are considered equal by Equals or by the overloaded == and != operators should have the same hash code.

Listing 2.1.28 Extend the Fraction Class
 1: //File        :part02_27.cs   2: //Author    :Richard L. Weeks   3: //Purpose    :Demonstrate operator overloading   4:   5:   6: using System;   7:   8:   9:  10: public class Fraction {  11:  12:     //data members  13:     private int    m_numerator;  14:     private int    m_denominator;  15:  16:  17:     //Properties  18:     public int Numerator {  19:         get { return m_numerator; }  20:         set { m_numerator = value; }  21:     }  22:     public int Denominator {  23:         get { return m_denominator; }  24:         set { m_denominator = value; }  25:     }  26:  27:  28:     //Constructors  29:     public Fraction( ) { m_numerator = 0; m_denominator = 0; }  30:  31:     public Fraction( int iNumerator, int iDenominator ) {  32:             m_numerator = iNumerator;  33:             m_denominator = iDenominator;  34:     }  35:  36:     //Arithmetic operators +,-,/,*  37:  38:     public static Fraction operator+(Fraction f1, Fraction f2) {  39:         Fraction Result = new Fraction( );  40:         //In order to add fractions, the denominators need to be the same  41:         //the fastest way is to multiply them together and adjust the numerators  42:         if( f1.Denominator != f2.Denominator ) {  43:             Result.Denominator = f1.Denominator * f2.Denominator;  44:             Result.Numerator   = (f1.Numerator * f2.Denominator) + (f2.Numerator * graphics/ccc.gif f1.Denominator);  45:         }  else {  46:             Result.Denominator = f1.Denominator;  47:             Result.Numerator  = f1.Numerator + f2.Numerator;  48:         }  49:         return Result;  50:     }  51:  52:  53:     public static Fraction operator-(Fraction f1, Fraction f2) {  54:         Fraction Result = new Fraction( );  55:         //To subtract fractions, the denominators need to be the same  56:         //the fastest way is to multiply them together and adjust the numerators  57:         if( f1.Denominator != f2.Denominator ) {  58:             Result.Denominator = f1.Denominator * f2.Denominator;  59:             Result.Numerator   = (f1.Numerator * f2.Denominator) - (f2.Numerator * graphics/ccc.gif f1.Denominator);  60:         }  else {  61:             Result.Denominator = f1.Denominator;  62:             Result.Numerator   = f1.Numerator - f2.Numerator;  63:         }  64:         return Result;  65:     }  66:  67:     //add an explicit casting operator from fraction to double  68:     public static explicit operator double(Fraction f) {  69:         double dResult = ((double)f.Numerator / (double)f.Denominator);  70:         return dResult;  71:     }  72:  73:  74:     public static bool operator==(Fraction f1, Fraction f2) {  75:         //TODO: Implement comparison of f1 to f2  76:         return true;  77:     }  78:  79:     public static bool operator!=(Fraction f1, Fraction f2) {  80:         return !(f1 == f2);  81:     }  82:  83:  84: }  85:  86:  87: public class OperatorTest {  88:  89:     public static void Main( ) {  90:  91:         Fraction f1 = new Fraction( 1, 5 );  92:         Fraction f2 = new Fraction( 2, 5 );  93:  94:         //Add the Fractions  95:         Fraction f3 = f1 + f2;  96:  97:         //Display the result  98:         Console.WriteLine("f1 + f2 = {0} /{1} ", f3.Numerator, f3.Denominator );  99: 100:         //Substract f2 from f3 should get f1 101:         f3 = f3 - f2; 102:         Console.WriteLine("f3 - f2 = {0} /{1} ", f3.Numerator, f3.Denominator ); 103: 104:         //Print f3 as a double 105:         Console.WriteLine("f3 as a double = {0} ", (double)f3); 106:     } 107: } 

Inheritance

Inheritance is a key concept in OO design and languages. Inheritance allows for common functionality and attributes to reside in a base class, and specialized classes can inherit the functionality provided by the base class. C# only supports single inheritance. C++ provides for multiple inheritances and, when used correctly, is a truly powerful paradigm. However, multiple inheritance has proven to be difficult to maintain and somewhat hard to follow. This is one reason that C# only implements single inheritance.

Figure 2.1.1 illustrates a common case of inheritance.

Figure 2.1.1. Basic Inheritance

graphics/0201fig01.gif

The base class Dog would contain attributes and methods common to all canines. Each derived class can then specialize the implementation as necessary. It is also important to note that C# only provides public inheritance (see Listing 2.1.29). Again, C++ developers may scream now.

Listing 2.1.29 Inheritance
 1: //File        :part02_28.cs  2: //Purpose    :Demonstrate basic inheritance  3: //  4:  5: using System;  6:  7: public class Dog {  8:  9:     //Common attributes 10:     public string Name; 11:     public int    Weight; 12: 13: 14:     //Common methods 15:     public void Speak( ) { Console.WriteLine("ruff!"); } 16:     public void DrinkWater( ) { Console.WriteLine("Gulp"); } 17: 18: } 19: 20: 21: //A specialized version of a Dog 22: public class GermanShepard: Dog { 23:     public void OnGuard( ) { Console.WriteLine("In Guard Mode"); } 24: } 25: 26: 27: public class JackRussell : Dog { 28:     public void Chew( ) {Console.WriteLine("I'm chewing your favorite shoes!"); } 29: } 30: 31: 32: 33: 34: 35: public class Inherit { 36: 37:     public static void Main( ) { 38: 39:         GermanShepard Simon = new GermanShepard( ); 40:         JackRussell Daisy   = new JackRussell( ); 41: 42:         //Both Simon and Daisy have a name and weight 43:         Simon.Name = "Simon"; Simon.Weight = 85; 44:         Daisy.Name = "Daisy"; Daisy.Weight = 25; 45: 46:         //Both Simon and Daisy have the speak and DrinkWater methods 47:         Simon.Speak( ); Simon.DrinkWater( ); 48:         Daisy.Speak( ); Daisy.DrinkWater( ); 49: 50: 51:         //Only Simon has the OnGuard Method 52:         Simon.OnGuard( ); 53: 54:         //Only Daisy has the Chew method 55:         Daisy.Chew( ); 56:     } 57: } 

Figure 2.1.1 has been implemented in C# and presented in Listing 2.1.29. Remember that C# only supports single inheritance, so it is not possible to create a derived class that specifies more than one base class. The syntax to inherit from a base class is to place the name of the base class to the right of a colon following the name of the derived class, as shown in the following:

 class Bar : Foo { } 

In this case, Bar is the derived class and Foo is the base class.

Polymorphism

The term polymorphism translates to "many forms." In OO terminology, the ability to override a base class method and provide a different implementation in a derived class is a basic form of polymorphism.

Consider the previous example where GermanShepard and JackRussell were derived from the common base class Dog . The base class Dog provides a method Speak that all derived classes inherit. However, if you've ever heard a German Shepard bark and a Jack Russell bark, they don't sound the same. Therefore, the base class Dog should allow for derived classes to provide their own implementation of the Speak method.

C# provides the keyword virtual to denote a method that can be overridden by a derived class. A derived class can override the virtual method by using the override keyword.

 class Dog {     public virtual void Speak( ) {...} } class JackRussell : Dog {     public override void Speak( ) {...} } 

The true power of polymorphism comes into play when the object type is unknown at compile time. This allows for runtime type information to be used to determine the actual method to be invoked. Listing 2.1.30 updates the Dog base class and provides for the virtual method Speak .

Listing 2.1.30 Virtual Methods
 1: //File        :part02_28.cs  2: //Purpose    :Demonstrate basic inheritance  3: //  4:  5: using System;  6:  7: public class Dog {  8:  9:     //Common attributes 10:     public string Name; 11:     public int    Weight; 12: 13: 14:     //Common methods 15:     public virtual void Speak( ) { Console.WriteLine("ruff!"); } 16:     public void DrinkWater( ) { Console.WriteLine("Gulp"); } 17: 18: } 19: 20: 21: //A specialized version of a Dog 22: public class GermanShepard : Dog { 23: 24:     public void OnGuard( ) { Console.WriteLine("In Guard Mode"); } 25: 26:     //override the Dog.Speak() method 27:     public override void Speak( ) { Console.WriteLine("RUFF,RUFF,RUFF"); } 28: } 29: 30: 31: public class JackRussell : Dog { 32:     public void Chew( ) {Console.WriteLine("I'm chewing your favorite shoes!"); } 33: 34:     //override the Dog.Speak() method 35:     public override void Speak( ) { Console.WriteLine("yap,yap,yap"); } 36: 37: } 38: 39: 40: 41: 42: 43: public class Inherit { 44: 45:     public static void Main( ) { 46: 47:         GermanShepard Simon = new GermanShepard( ); 48:         JackRussell Daisy   = new JackRussell( ); 49: 50:         //Both Simon and Daisy have a name and weight 51:         Simon.Name = "Simon"; Simon.Weight = 85; 52:         Daisy.Name = "Daisy"; Daisy.Weight = 25; 53: 54:         //Polymorphism allows for the proper method to be called 55:         //based on runtime type information 56:         Dog d = Simon; 57:         d.Speak( );        //calls GermanShepard.Speak( ); 58: 59:         d = Daisy; 60:         d.Speak( );        //calls JackRussell.Speak( ); 61: 62:     } 63: } 

The revised inheritance example now makes use of a virtual method Speak . Both GermanShepard and JackRussell derived classes provide a specialized implementation of the base class Dog.Speak method.

Nothing real exciting happens until line 56. Notice the declaration of a variable of type Dog and its initialization with a reference to Simon . Now is when things get interesting. When the Speak method is invoked, the runtime type of the object is determined and, based on that information, the proper implementation of Speak is invoked ”in this case, GermanShepard.Speak() . Following the invocation of d .Speak() , the variable d is set to reference Daisy . Again, d.Speak() is invoked and the runtime type information is accessed to determine the proper Speak method to invoke.

Polymorphism represents a powerful paradigm in the OO world. When applied properly, components can interact with each other without having intimate knowledge of each other.

I l @ ve RuBoard


C# and the .NET Framework. The C++ Perspective
C# and the .NET Framework
ISBN: 067232153X
EAN: 2147483647
Year: 2001
Pages: 204

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net