Chapter 7. Classes and Inheritance

Class Inheritance

Inheritance allows you to define a new class that incorporates and extends an already declared class.

  • You can use an existing class, called the base class, as the basis for a new class, called the derived class. The members of the derived class consist of the following:

    • The members in its own declaration

    • The members of the base class

  • To declare a derived class, you add a class-base specification after the class name. The class-base specification consists of a colon, followed by the name of the class to be used as the base class. The derived class is said to directly inherit from the base class listed.

  • A derived class is said to extend its base class, because it includes the members of the base class plus any additional functionality provided in its own declaration.

  • A derived class cannot delete any of the members it has inherited.

For example, the following shows the declaration of a class called OtherClass, which is derived from a class called SomeClass:

Class Inheritance

Figure 7-1 shows an instance of each of the classes. Class SomeClass, on the left, has one field and one method. Class OtherClass, on the right, is derived from SomeClass and contains an additional field and an additional method.

Base class and derived class

Figure 7-1. Base class and derived class

Accessing the Inherited Members

Inherited members are accessed just as if they had been declared in the derived class itself. (Inherited constructors are a bit different—I'll cover them later in the chapter.) For example, the following code declares classes SomeClass and OtherClass, which were shown in Figure 7-1. The code shows that all four members of OtherClass can be seamlessly accessed, regardless of whether they are declared in the base class or the derived class.

  • Main creates an object of derived class OtherClass.

  • The next two lines in Main call Method1 in the base class, using Field1 from the base class, and then Field2 from the derived class.

  • The subsequent two lines in Main call Method2 in the derived class, again using Field1 from the base class and then Field2 from the derived class.

class SomeClass {                          // Base class
   public string Field1 = "base class field";
   public void Method1( string value ) {
      Console.WriteLine("Base class -- Method1:     {0}", value);
   }
}


class OtherClass: SomeClass {             // Derived class
   public string Field2 = "derived class field";
   public void Method2( string value ) {
      Console.WriteLine("Derived class -- Method2:  {0}", value);
   }
}

class Program {
   static void Main() {
      OtherClass oc = new OtherClass();

      oc.Method1( oc.Field1 );       // Base method with base field
      oc.Method1( oc.Field2 );       // Base method with derived field
      oc.Method2( oc.Field1 );       // Derived method with base field
      oc.Method2( oc.Field2 );       // Derived method with derived field
   }
 }

This code produces the following output:

Base class -- Method1:     base class field
Base class -- Method1:     derived class field
Derived class -- Method2:  base class field
Derived class -- Method2:  derived class field

All Classes Are Derived from Class object

All classes, except special class object, are derived classes, even if they don't have a class-base specification. Class object is the only one that is not derived, since it is the base of the inheritance hierarchy.

Classes without a class-base specification are implicitly derived directly from class object. Leaving off the class-base specification is just shorthand for specifying that object is the base class. The two forms are semantically equivalent.

Figure 7-2 shows both forms of declaration for the same class.

Direct inheritance from object

Figure 7-2. Direct inheritance from object

Other important facts about class derivation are the following:

  • A class declaration can have only a single class listed in its class-base specification. This is called single inheritance.

  • Although a class can directly inherit from only a single base class, there is no limit to the level of derivation. That is, the class listed as the base class might be derived from another class, which is derived from another class, and so forth, until you eventually reach object.

Base class and derived class are relative terms. All classes are derived classes, either from object or from another class—so generally when we call a class a derived class, we mean that it is immediately derived from some class other than object. Figure 7-3 shows a simple class hierarchy. After this, I will not show object in the figures, since all classes are ultimately derived from it.

A class hierarchy

Figure 7-3. A class hierarchy

Hiding Members of a Base Class

Although a derived class cannot delete any of the members it has inherited, it can hide them.

  • To hide an inherited data member, declare a new member of the same type and with the same name.

  • You can hide, or mask, an inherited function member by declaring in the derived class a new function member with the same signature. Remember that the signature consists of the name and parameter list, but does not include the return type.

  • To let the compiler know that you are purposely hiding an inherited member, use the new modifier. Without it, the program will compile successfully, but the compiler will warn you that you are hiding an inherited member.

  • You can also hide static members.

The following code declares a base class and a derived class, each with a string member called Field1. The keyword new is used to explicitly tell the compiler to mask the base class member. Figure 7-4 illustrates an instance of each class.

Hiding Members of a Base Class
Hiding a member of a base class

Figure 7-4. Hiding a member of a base class

In the following code, OtherClass derives from SomeClass but hides both its inherited members. Note the use of the new modifier. The code is illustrated in Figure 7-5.

Hiding a member of a base class
Hiding a field and a method of the base class

Figure 7-5. Hiding a field and a method of the base class

Base Access

Sometimes, your derived class might need to access a hidden inherited member. You can access a hidden base class member by using a base access expression. This expression consists of the keyword base, followed immediately by a period and the name of the member, as shown here:

Base Access

For example, in the following code, derived class OtherClass hides Field1 in its base class but accesses it by using a base access expression.

Base Access

This code produces the following output:

Field1 -- In the derived class
Field1 -- In the base class

Using References to a Base Class

An instance of a derived class consists of an instance of the base class, plus the additional members of the derived class. A reference to the derived class points to the whole class object, including the base class part.

If you have a reference to a derived class object, you can get a reference to the base class part of the object by casting the reference to the type of the base class by using the cast operator. The cast operator is placed in front of the object reference, and consists of a set of parentheses containing the name of the class being cast to. Casting is covered in detail in Chapter 18.

The next few sections will cover accessing an object by using a reference to the base class part of the object. We'll start by looking at the two lines of code that follow, which declare references to objects. Figure 7-6 illustrates the code and shows the parts of the object seen by the different variables.

  • The first line declares and initializes variable derived, which then contains a reference to an object of type MyDerivedClass.

  • The second line declares a variable of the base class type, MyBaseClass, and casts the reference in derived to that type, giving a reference to the base class part of the object.

    • The reference to the base class part is stored in variable mybc, on the left side of the assignment operator.

    • The reference to the base class part cannot "see" the rest of the derived class object, because it is "looking" at it through a reference to the base type.

MyDerivedClass derived = new MyDerivedClass();      // Create an object.
MyBaseClass mybc       = (MyBaseClass) derived;     // Cast the reference.
Reference derived can see the entire MyDerivedClass object, while mybc can only see the MyBaseClass part of the object.

Figure 7-6. Reference derived can see the entire MyDerivedClass object, while mybc can only see the MyBaseClass part of the object.

The following code shows the declaration and use of these two classes. Figure 7-7 illustrates the object and references in memory.

Main creates an object of type MyDerivedClass and stores its reference in variable derived. Main also creates a variable of type MyBaseClass and uses it to store a reference to the base class portion of the object. When the Print method is called on each reference, the call invokes the implementation of the method that that reference can see, producing different output strings.

Reference derived can see the entire MyDerivedClass object, while mybc can only see the MyBaseClass part of the object.

This code produces the following output:

This is the derived class.
This is the base class.
A reference to the derived class and the base class

Figure 7-7. A reference to the derived class and the base class

Virtual and Override Methods

In the previous section, you saw that when you access an object of a derived class by using a reference to the base class, you get the members from the base class. Virtual methods allow a reference to the base class, to access "up into" the derived class.

You can use a reference to a base class to call a method in the derived class, if the following are true:

  • The method in the derived class and the method in the base class each have the same signature and return type.

  • The method in the base class is labeled virtual.

  • The method in the derived class is labeled override.

For example, the following code shows the virtual and override modifiers on the methods in the base class and derived class.

Virtual and Override Methods

Figure 7-8 illustrates this set of virtual and override methods. Notice how the behavior differs from the previous case, where I used new to hide the base class members.

  • When the Print method is called by using the reference to the base class (mybc), the method call is passed up to the derived class and executed, because

    • The method in the base class is marked as virtual.

    • There is a matching override method in the derived class.

  • Figure 7-8 illustrates this by showing the arrow coming out the back of the virtual Print method and pointing at the override Print method.

A virtual method and an override method

Figure 7-8. A virtual method and an override method

The following code is the same as in the previous section, but this time, the methods are labeled virtual and override. This produces a result that is very different from that of the previous example. In this version, calling the method through the base class invokes the method in the derived class.

A virtual method and an override method

This code produces the following output:

This is the derived class.
This is the derived class.

Other important information about the virtual and override modifiers is the following:

  • The overriding and overridden methods must have the same accessibility. In other words, the overridden method cannot be, for example, private, and the overriding method public.

  • You cannot override a method that is static or is non-virtual.

  • Methods, properties, and indexers (which I covered in the preceding chapter), and another member type, called events (which I will cover later in the text), can all be declared virtual and override.

Overriding a Method Marked override

Overriding methods can occur between any levels of inheritance.

  • When you use a reference to the base class part of an object to call an overridden method, the method call is passed up the derivation hierarchy for execution, to the most-derived version of the method marked as override.

  • If there are other declarations of the method at higher levels of derivation, which are not marked as override—they are not invoked.

For example, the following code shows three classes that form an inheritance hierarchy: MyBaseClass, MyDerivedClass, and SecondDerived. All three classes contain a method named Print, with the same signature. In MyBaseClass, Print is labeled virtual. In MyDerivedClass, it is labeled override. In class SecondDerived, you can declare method Print with either override or new. Let's look at what happens in each case.

class MyBaseClass                                    // Base class
   {
      virtual public void Print()
      { Console.WriteLine("This is the base class."); }
   }

   class MyDerivedClass : MyBaseClass                   // Derived class
   {
      override public void Print()
      { Console.WriteLine("This is the derived class."); }
   }

   class SecondDerived : MyDerivedClass                 // Most-derived class
   {
      ... // Given in the following pages
   }

Case 1: Declaring Print with override

If you declare the Print method of SecondDerived as override, then it will override both the less-derived versions of the method, as shown in Figure 7-9. If a reference to the base class is used to call Print, it gets passed all the way up the chain to the implementation in class SecondDerived.

The following code implements this case. Notice the code in the last two lines of method Main.

  • The first of the two statements calls the Print method by using a reference to the most-derived class—SecondDerived. This is not calling through a reference to the base class portion, so it will call the method implemented in SecondDerived.

  • The second statement, however, calls the Print method by using a reference to the base class—MyBaseClass.

Case 1: Declaring Print with override

The result is that regardless of whether Print is called through the derived class or the base class, the method in the most-derived class is called. When called through the base class, it is passed up the inheritance hierarchy. This code produces the following output:

This is the second derived class.
This is the second derived class.
Execution is passed to the top of the chain of multiple levels of override.

Figure 7-9. Execution is passed to the top of the chain of multiple levels of override.

Case 2: Declaring Print with new

If instead you declare the Print method of SecondDerived as new, the result is as shown in Figure 7-10. Main is the same as in the previous case.

class SecondDerived : MyDerivedClass {
      new public void Print() {
         Console.WriteLine("This is the second derived class.");
      }
   }

   class Program {
      static void Main()                                    // Main
      {
         SecondDerived derived = new SecondDerived();       // Use SecondDerived.
         MyBaseClass mybc = (MyBaseClass)derived;           // Use MyBaseClass.

         derived.Print();
         mybc.Print();
      }
   }

The result is that when method Print is called through the reference to SecondDerived, the method in SecondDerived is executed, as you would expect. When the method is called through a reference to MyBaseClass, however, the method call is passed up only one level, to class MyDerived, where it is executed. The only difference between the two cases is whether the method in SecondDerived is declared with modifier override or modifier new.

This code produces the following output:

This is the second derived class.
This is the derived class.
Hiding the overridden methods

Figure 7-10. Hiding the overridden methods

Constructor Execution

In the preceding chapter, you saw that a constructor executes code that prepares a class for use. This includes initializing both the static and instance members of the class. In this chapter, you saw that part of a derived class object is an object of the base class.

  • To create the base class part of an object, a constructor for the base class is called as part of the process of creating the instance.

  • Each class in the inheritance hierarchy chain executes its base class constructor before it executes its own constructor body.

For example, the following code shows a declaration of class MyDerivedClass and its constructor. When the constructor is called, it calls the parameterless constructor MyBaseClass() before executing its own body.

class MyDerivedClass : MyBaseClass
{
   MyDerivedClass()       // Constructor uses base constructor MyBaseClass().
   {
      ...
   }

The order of construction is shown in Figure 7-11. When an instance is being created, one of the first things that is done is the initialization of all the instance members of the object. After that, the base class constructor is called. Only then is the body of the constructor of the class itself executed.

Order of object construction

Figure 7-11. Order of object construction

For example, in the following code, the values of MyField1 and MyField2 would be set to 5 and 0, respectively, before the base class constructor is called.

class MyDerivedClass : MyBaseClass
   {
      int MyField1 = 5;                      // 1. Member initialized
      int MyField2;                          //    Member initialized

      public MyDerivedClass()                // 3. Body of constructor executed
      {
         ...
      }
   }

   class MyBaseClass
   {
      public MyBaseClass()                   // 2. Base class constructor called
      {
         ...
      }
   }

Warning

Caution Calling a virtual method in a constructor is strongly discouraged. The virtual method in the base class would call the override method in the derived class while the base class constructor is being executed. But that would be before the derived constructor's body is executed. It would, therefore, be calling up into the derived class before the class is completely initialized.

Constructor Initializers

By default, the parameterless constructor of the base class is called when an object is being constructed. But constructors can be overloaded, so a base class might have more than one. If you want your derived class to use a specific base class constructor other than the parameterless constructor, you must specify it in a constructor initializer.

There are two forms of constructor initializer:

  • The first form uses the keyword base and specifies which base class constructor to use.

  • The second form uses the keyword this and specifies which other constructor from this class should be used.

A base class constructor initializer is placed after a colon following the parameter list in a class's constructor declaration. The constructor initializer consists of the keyword base, and the parameter list of the base constructor to call.

For example, the following code shows a constructor for class MyDerivedClass.

  • The constructor initializer specifies that the base class constructor to use is the one that has two parameters; the first parameter is a string, and the second parameter is an int.

  • The parameters in the base parameter list must match the intended base constructor's parameter list, in type and order.

Constructor Initializers

When you declare a constructor without a constructor initializer, it is a shortcut for the form with a constructor initializer consisting of base(), as illustrated in Figure 7-12. The two forms are semantically equivalent.

Equivalent forms of a constructor

Figure 7-12. Equivalent forms of a constructor

Another form of constructor initializer instructs the constructor to use a different constructor in the same class. For example, the following shows a constructor for class MyClass, which uses the constructor from the same class, but with two parameters, supplying a default parameter as the second one.

Equivalent forms of a constructor

Class Access Modifiers

A class can be seen and accessed by other classes in the system. This section covers the accessibility of classes. Although I will use classes in the explanations and examples since that is what we've covered so far in the text, the accessibility rules also apply to the other types I will cover later.

The term visible is sometimes used for the term accessible. They can be used interchangeably. There are two levels of class accessibility: public and internal.

  • A class marked public can be accessed by code from any assembly in the system. To make a class visible to other assemblies, use the public access modifier, as shown here:

    Class Access Modifiers
  • A class marked internal can only be seen by classes within its own assembly.

    • This is the default accessibility level, so unless you explicitly specify the modifier public in the class declaration, code outside the assembly cannot access the class.

    • You can explicitly declare a class as internal by using the internal access modifier.

Class Access Modifiers

Figure 7-13 illustrates the accessibility of internal and public classes from outside the assembly. Class MyClass is not visible to the classes in the assembly on the left, because it is marked internal. Class OtherClass, however, is visible to the classes on the left, because it is marked public.

Classes from other assemblies can access public classes but cannot access internal classes.

Figure 7-13. Classes from other assemblies can access public classes but cannot access internal classes.

Inheritance Between Assemblies

So far, I have been declaring derived classes in the same assembly where the base class is declared. But C# also allows you to derive a class from a base class defined in a different assembly. To do this, the following must be true:

  • The base class must be declared public, so that it can be accessed from outside its assembly.

  • You must include a reference in your Visual Studio project to the assembly containing the base class.

To make it easier to refer to the classes and types in the other assembly, without using their fully qualified names, place a using directive at the top of the source file, with the namespace containing the classes or types you want to access.

Note

Adding a reference to the other assembly and adding a using directive are two separate things. Adding the reference to the other assembly tells the compiler where the required types are defined. Adding the using directive allows you to reference other classes without having to use their fully qualified names. Chapter 10 covers this in detail.

For example, the following two code segments, from different assemblies, show how easy it is to inherit a class from another assembly. The first code listing creates an assembly that contains the declaration of a class called MyBaseClass, which has the following characteristics:

  • It is declared in a source file called Assembly1.cs, and inside a namespace declared as BaseClassNS.

  • It is declared public, so that it can be accessed from other assemblies.

  • It contains a single member, a method called PrintMe, that just writes out a simple message identifying the class.

Inheritance Between Assemblies

The second assembly contains the declaration of a class called DerivedClass, which inherits from MyBaseClass, declared in the first assembly. The source file is named Assembly2.cs. Figure 7-14 illustrates the two assemblies.

  • DerivedClass has an empty body but inherits method PrintMe from MyBaseClass.

  • Main creates an object of type DerivedClass and calls its inherited method PrintMe.

Inheritance Between Assemblies

This code produces the following output:

I am MyBaseClass
Inheriting across assemblies

Figure 7-14. Inheriting across assemblies

Member Access Modifiers

Class accessibility was covered earlier in the chapter. With class accessibility, there are only two modifiers—internal and public. This section covers member accessibility. Class accessibility describes the visibility of a class; member accessibility describes the visibility of class members.

Each member declared in a class is visible to various parts of the system, depending on the access modifier assigned to it in the class declaration. You've seen that private members are visible only to other members of the same class, while public members can be visible to classes outside the assembly as well. In this section, we will look again at the public and private access levels, as well as the three other levels of accessibility.

Before looking at the specifics of member accessibility, there are some general things we need to cover first:

  • All members explicitly declared in a class's declaration are visible to each other, regardless of their accessibility specification.

  • Inherited members are not explicitly declared in a class's declaration, so, as you shall see, inherited members might or might not be visible to members of a derived class.

  • There are five member access levels:

    • public

    • private

    • protected

    • internal

    • protected internal

  • You must specify member access levels on a per-member basis. If you don't specify an access level for a member, its implicit access level is private.

  • A member cannot be more accessible than its class. That is, if a class has an accessibility level limiting it to the assembly, individual members of the class cannot be seen outside the assembly, regardless of their access modifiers.

Regions Accessing a Member

The member access modifiers in a class's declaration specify which other types can and cannot access which members of the class. For example, the following declaration shows members declared with the five access levels.

public class MyClass
   {
      public             int Member1;
      private            int Member2;
      protected          int Member3;
      internal           int Member4;
      protected internal int Member5;
      ...

The access levels are based on two characteristics with regard to the class being declared:

  • Whether the class is derived from the class being declared

  • Whether a class is in the same assembly as the class being declared

These two characteristics yield four groups, as illustrated in Figure 7-15. In relation to the class being declared, another class can be any of the following:

  • In the same assembly and derived from it (bottom right)

  • In the same assembly but not derived from it (bottom left)

  • In a different assembly and derived from it (top right)

  • In a different assembly and not derived from it (top left)

These characteristics are used to define the five access levels.

Areas of accessibility

Figure 7-15. Areas of accessibility

Public Member Accessibility

The public access level is the least restrictive. All classes both inside and outside the assembly have free access to the member. Figure 7-16 illustrates the accessibility of a public class member of MyClass.

To declare a member public, use the public access modifier, as shown.

Public Member Accessibility
A public member of a public class is visible to all classes in the same assembly or other assemblies.

Figure 7-16. A public member of a public class is visible to all classes in the same assembly or other assemblies.

Private Member Accessibility

The private access level is the most restrictive.

  • A private class member can be accessed only by members of its own class. It cannot be accessed by other classes, including classes that are derived from it.

  • A private member can, however, be accessed by members of classes nested in its class. Nested classes are covered in Chapter 25.

Figure 7-17 illustrates the accessibility of a private member.

A private member of any class is visible only to members of its own class (or nested classes).

Figure 7-17. A private member of any class is visible only to members of its own class (or nested classes).

Protected Member Accessibility

The protected access level is like the private access level, except that it allows classes derived from the class to access the member. Figure 7-18 illustrates protected accessibility. Notice that even classes outside the assembly that are derived from the class have access to the member.

A protected member of a public class is visible to members of its own class or classes derived from it. The derived classes can even be in other assemblies.

Figure 7-18. A protected member of a public class is visible to members of its own class or classes derived from it. The derived classes can even be in other assemblies.

Internal Member Accessibility

Members marked internal are visible to all the classes in the assembly, but not to classes outside the assembly, as shown in Figure 7-19.

An internal member of a public class is visible to members of any class in the same assembly, but not to classes outside the assembly.

Figure 7-19. An internal member of a public class is visible to members of any class in the same assembly, but not to classes outside the assembly.

Protected Internal Member Accessibility

Members marked protected internal are visible to all the classes that inherit from the class, and also to all classes inside the assembly, as shown in Figure 7-20. Notice that the set of classes allowed access is the combined set of classes allowed by the protected modifier plus the set of classes allowed by the internal modifier. Notice that this is the union of protected and internal—not the intersection.

A protected internal member of a public class is visible to members of classes in the same assembly or to members of classes derived from that class. It is not visible to classes in other assemblies that are not derived from the class.

Figure 7-20. A protected internal member of a public class is visible to members of classes in the same assembly or to members of classes derived from that class. It is not visible to classes in other assemblies that are not derived from the class.

Summary of Member Access Modifiers

The following two tables summarize the characteristics of the five member access levels. Table 7-1 lists the modifiers and gives an intuitive summary of the effects of the modifier.

Table 7-1. Member Access Modifiers

Modifier

Meaning

private

Accessible only within the class

internal

Accessible to all classes within this assembly

protected

Accessible to all classes derived from this class

protected internal

Accessible to all classes that are either derived from this class or declared within this assembly

public

Accessible to any class

Table 7-2 lists the access modifiers down the left side of the table, and the categories of classes across the top. Derived refers to classes derived from the class declaring the member. Non-derived means classes not derived from the class declaring the member. A check in a cell means that that category of class can access members with the corresponding modifier.

Table 7-2. Summary of Member Accessibility

 

Classes in Same Assembly

Classes in Different Assembly

 

Non-Derived

Derived

Non-Derived

Derived

private

    

internal

Summary of Member Accessibility
Summary of Member Accessibility
  

protected

 
Summary of Member Accessibility
 
Summary of Member Accessibility

protected internal

Summary of Member Accessibility
Summary of Member Accessibility
 
Summary of Member Accessibility

public

Summary of Member Accessibility
Summary of Member Accessibility
Summary of Member Accessibility
Summary of Member Accessibility

Abstract Members

An abstract member is a function member that is designed to be overridden. An abstract member has the following characteristics:

  • It is marked with the abstract modifier.

  • It does not have an implementation code block. The code blocks of abstract members are replaced with semicolons.

For example, the following code from inside a class definition declares two abstract members: an abstract method called PrintStuff and an abstract property called MyProperty. Notice the semicolons in place of the implementation blocks.

Abstract Members

Other important facts about abstract members are the following:

  • Abstract methods, although they must be overridden by a corresponding method in a derived class, cannot use the virtual modifier in addition to the abstract modifier.

  • As with virtual methods, the implementation of an abstract method in a derived class must specify the override modifier.

  • Abstract members can be declared only in abstract classes, which we will look at in the next section.

Table 7-3 compares and contrasts virtual members and abstract members.

Table 7-3. Comparing Virtual and Abstract Members

 

Virtual Member

Abstract Member

Keyword

virtual

abstract

Implementation body

Has an implementation body

No implementation body—semicolon instead

Overridden in a derived class

Can be overridden—using override

Must be overridden—using override

Types of members

Methods

Methods

 

Properties

Properties

 

Events

Events

 

Indexers

Indexers

Abstract Classes

An abstract class can be used only as the base class of another class. Abstract classes are designed to be inherited from.

  • You cannot create instances of an abstract class.

  • An abstract classis declared using the abstract modifier.

    Abstract Classes
  • An abstract class can contain abstract members, but that is not a requirement. The members of an abstract class can be any combination of abstract members and normal members with implementations.

  • An abstract class can itself be derived from another abstract class. For example, the following code shows one abstract class derived from another.

    abstract class AbClass                         // Abstract class
       {
          ...
       }
    
       abstract class MyAbClass : AbClass           // Abstract class derived from
       {                                            // an abstract class
          ...
       }
  • Any class derived from an abstract class must implement all the abstract members of the class by using the override keyword, unless the derived class is itself abstract.

Example of an Abstract Class and an Abstract Method

The following code shows an abstract class called AbClass with two methods.

The first method is a normal method with an implementation that prints out the name of the class. The second method is an abstract method that must be implemented in a derived class. Class DerivedClass inherits from AbClass, and implements and overrides the abstract method. Main creates an object of DerivedClass and calls its two methods.

Example of an Abstract Class and an Abstract Method

This code produces the following output:

I am AbClass
I am DerivedClass

Sealed Classes

In the previous section, you saw that an abstract class must be used as a base class—it cannot be instantiated as a stand-alone class. The opposite is true of a sealed class.

  • A sealed class can be used only as a stand-alone class—it cannot be used as a base class.

  • A sealed class is labeled with the sealed modifier.

For example, the following class is a sealed class. Any attempt to use it as the base class of another class will produce a compile error.

Sealed Classes

Static Classes

A static class is a class where all the members are static. Static classes are used to group data and functions that are not affected by instance data. A common use of a static class might be to create a math library containing sets of mathematical methods.

The important things to know about static classes are the following:

  • The class itself must be marked static.

  • All the members of the class must be static.

  • The class can have a static constructor, but not an instance constructor. You cannot create an instance of the class.

  • You cannot inherit from static classes—they're sealed.

You access the members just as you would access any static member, by using the class name and the member name.

The following code shows an example of a static class:

Static Classes

This code produces the following output:

3 is odd is True.
3 * 2 = 6.

Extension Methods

So far in this text, every method you've seen has been associated with the class in which it is declared. The extension method feature of C# 3.0 extends that boundary, allowing you to write methods associated with classes other than the class in which they are declared.

To see how you might use this feature, take a look at the following code. It contains class MyData, which stores three values of type double, and contains a constructor and a method called Sum, which returns the sum of the three stored values.

class MyData
   {
      private double D1;                                     // Fields
      private double D2;
      private double D3;

      public MyData(double d1, double d2, double d3)         // Constructor
         { D1 = d1; D2 = d2; D3 = d3; }

      public double Sum()                                    // Method Sum
      {
         return D1 + D2 + D3;
      }
   }

This is a pretty limited class, but suppose it would be more useful if it contained another method, which returned the average of the three data points. With what you know so far about classes, there are several ways you might implement the additional functionality:

  • If you have the source code and can modify the class, you could, of course, just add the new method to the class.

  • If, however, you can't modify the class—for example, if the class is in a third-party class library—then, as long as it isn't sealed, you could use it as a base class and implement the additional method in a class derived from it.

If, however, you don't have access to the code, or the class is sealed, or there is some other design reason that neither of these solutions will work, then you will have to write a method in another class that uses the publicly available members of the class.

For example, you might write a class like the one in the following code. It contains a static class called ExtendMyData, which contains a static method called Average, which implements the additional functionality. Notice that the method takes an instance of MyData as a parameter.

Extension Methods

This code produces the following output:

Average: 4

Although this is a perfectly fine solution, it would be more elegant if you could call the method on the class instance itself, rather than creating an instance of another class to act on it. The following two lines of code illustrate the difference. The first uses the method just shown—invoking a static method on an instance of another class. The second shows the form we would like to use—invoking an instance method on the object itself. Extension methods allow you to use the second form, even though the first form would be the normal way of writing the invocation.

ExtendMyData.Average( md )               // Static invocation form
   md.Average();                         // Instance invocation form

By making a small change in the declaration of method Average, you can use the instance invocation form. The change you need to make is to add the keyword this before the type name in the parameter declaration as shown following. Adding the this keyword to the first parameter of the static method of the static class changes it from a regular method of class ExtendMyData into an extension method of class MyData. You can now use both invocation forms.

Extension Methods

The important requirements for an extension method are the following:

  • The extension method must be declared static.

  • The class in which the extension method is declared must also be declared static.

  • The extension method must contain as its first parameter type the keyword this, followed by the name of the class it is extending.

Figure 7-21 illustrates the structure of an extension method.

The structure of an extension method

Figure 7-21. The structure of an extension method

The following code shows a full program, including class MyData and extension method Average declared in class ExtendMyData. Notice that method Average is invoked exactly as if it were an instance member of MyData! Figure 7-21 illustrates the code. Classes MyData and ExtendMyData together act like the desired class, with three methods.

The structure of an extension method

This code produces the following output:

Sum:     12
Average: 4

External Methods

An external method is a method that does not have an implementation in the declaration. Often the implementation is in a language other than C#.

  • External methods are marked with the extern modifier and do not have an implementation in the class declaration. The implementation is replaced by a semicolon.

    External Methods
  • Connecting the declaration with the implementation is implementation-dependent, but is often done using the DllImport attribute. Attributes are covered in detail in Chapter 24.

For example, the following code uses an external method, GetCurrentDirectory, whose implementation is the Win32 system call for getting a string that contains the current directory.

using System;
   using System.Text;
   using System.Runtime.InteropServices;

   namespace ExternalMethod
   {
      class MyClass
      {
         [DllImport("kernel32", SetLastError=true)]
         public static extern int GetCurrentDirectory(int a, StringBuilder b);
      }

      class Program
      {
         static void Main( )
         {
            const int MaxDirLength = 250;
            StringBuilder sb = new StringBuilder();
            sb.Length = MaxDirLength;

            MyClass.GetCurrentDirectory(MaxDirLength, sb);
            Console.WriteLine(sb);
         }
      }
   }

This code produces the following output:

C:BookProgramsExternalMethodExternalMethodinDebug
..................Content has been hidden....................

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