C H A P T E R  7

Image

Classes and Inheritance

Image Class Inheritance

Image Accessing the Inherited Members

Image Hiding Members of a Base Class

Image Base Access

Image Using References to a Base Class

Image Constructor Execution

Image Inheritance Between Assemblies

Image Member Access Modifiers

Image Abstract Members

Image Abstract Classes

Image Sealed Classes

Image External Methods

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:

Image

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.

Image

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're 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 class 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, as shown in Figure 7-2.

Image

Figure 7-2. The class declaration on the left implicitly derives from class object, while the one on the right explicitly derives from object. The two forms are semantically equivalent.

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 won't show object in the figures, since all classes are ultimately derived from it.

Image

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.

Image

Image

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.

Image

This code produces the following output:


OtherClass.Method1:  OtherClass Field1
Image

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

Base Access

If your derived class absolutely must access a hidden inherited member, you can access it 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:

Image

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

Image

This code produces the following output:


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

If you use this feature frequently, you might want to revaluate the design of your classes. Generally there are more elegant designs, but the feature is there if there's a situation where nothing else will do.

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 just 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 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's “looking” at it through a reference to the base type.
   MyDerivedClass derived = new MyDerivedClass();      // Create an object.
   MyBaseClass mybc       = (MyBaseClass) derived;     // Cast the reference.
Image

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 the reference can see, producing different output strings.

Image

This code produces the following output:


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

Image

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:

Image

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.
Image

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.

Image

This code produces the following output:


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

Other important things to know about the virtual and override modifiers are 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 nonvirtual.
  • Methods, properties, and indexers (which I covered in the preceding chapter), and another member type, called events (which I'll 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 that 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's 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.

Image

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's passed up the inheritance hierarchy. This code produces the following output:


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

Image

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.

Image

Figure 7-10. Hiding the overridden methods

Overriding Other Member Types

In the previous few sections, you've seen how the virtual/override designations work on methods. These work exactly the same way with properties, events, and indexers. For example, the following code shows a read-only property named MyProperty using virtual/override.

   class MyBaseClass
   {
      private int _myInt = 5;
      virtual public int MyProperty
      {
         get { return _myInt; }
      }
   }
   
   class MyDerivedClass : MyBaseClass
   {
      private int _myInt = 10;
      override public int MyProperty
      {
         get { return _myInt; }
      }
   }
   
   class Program
   {
      static void Main()
      {
         MyDerivedClass derived = new MyDerivedClass();
         MyBaseClass mybc       = (MyBaseClass)derived;

         Console.WriteLine( derived.MyProperty );
         Console.WriteLine( mybc.MyProperty );
      }
   }

This code produces the following output:


10
10

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 implicitly 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().
      {
         ...
      }

Figure 7-11 shows the order of construction. 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.

Image

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
      {
         ...
      }
   }

Image 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 construction process should call the base class constructor with two parameters, where 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.

Image

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

Image

Figure 7-12. Equivalent forms of a constructor

The other form of constructor initializer instructs the construction process (actually, the compiler) to use a different constructor from the same class. For example, the following shows a constructor with a single parameter for class MyClass. That single-parameter constructor, however, uses a constructor from the same class, but with two parameters, supplying a default parameter as the second one.

Image

Another situation where this comes in particularly handy is where you have several constructors for a class, and they have common code that should always be performed at the beginning of the object construction process.  In this case, you can factor out that common code and place it in a constructor that is used as a constructor initializer by all the other constructors. As a matter of fact, this is a suggested practice since it reduces code duplication.

You might think that you could just declare another method that performs those common initializations and have all the constructors call that method. This isn't as good for several reasons. The first is that the compiler can optimize certain things when it knows a method is a constructor. The second is that there are some things that can be done only in a constructor and not elsewhere. For example, in the previous chapter you learned that readonly fields can be initialized only inside a constructor. You will get a compiler error if you attempt to initialize a readonly field in any other method, even if that method is called by a constructor only.

Going back to that common constructor, if it can stand on its own as a valid constructor that initializes everything in the class that needs to be initialized, then it's perfectly fine to leave it as a public constructor.

What if, however, it doesn't completely initialize an object? In that case, you mustn't allow that constructor to be callable from outside the class, since it would then create incompletely initialized objects. To avoid that problem, you can declare the constructor private instead of public, as shown in the following code:

   class MyClass
   {
      readonly int    firstVar;
      readonly double secondVar;

      public string UserName;
      public int UserIdNumber;

      private MyClass( )            // Private constructor performs initializations
      {                             // common to the other constructors.
         firstVar  = 20;
         secondVar = 30.5;
      }

      public MyClass( string firstName ) : this() // use constructor initializer
      {
         UserName     = firstName;
         UserIdNumber = -1;
      }

      public MyClass( int idNumber ) : this( )    // use constructor initializer
      {
         UserName     = "Anonymous";
         UserIdNumber = idNumber;
      }
   }

Class Access Modifiers

A class can be seen and accessed by other classes in the system. This section explains the accessibility of classes. Although I'll use classes in the explanations and examples since that's what we've covered so far in the text, the accessibility rules also apply to the other types I'll 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:
    Image
  • 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.
      Image

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's marked internal. Class OtherClass, however, is visible to the classes on the left, because it's marked public.

Image

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

Inheritance Between Assemblies

So far, I've been declaring derived classes in the same assembly that contains the base class. 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.

Image 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's declared in a source file called Assembly1.cs and inside a namespace declared as BaseClassNS.
  • It's 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.

Image

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.

Image

This code produces the following output:


I am MyBaseClass
Image

Figure 7-14. Inheriting across assemblies

Member Access Modifiers

The previous two sections explained class accessibility. 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 the members of a class object.

Each member declared in a class is visible to various parts of the system, depending on the access modifier assigned to it in its 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'll 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'll 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, even public.

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.

Image

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.

Image

Image

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.

Image

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 also 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.

Image

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 illustrated in Figure 7-19.

Image

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.

Image

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's 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

Figure 7-21 shows the relative accessibility of the five member access modifiers.

Image

Figure 7-21. Relative accessibility of the various member access modifiers

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. Nonderived means classes not derived from the class declaring the member. A check in a cell means that the 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 Image Image
protected Image Image
protected internal Image Image Image
public Image Image Image Image

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 doesn't have an implementation code block. The code blocks of abstract members are represented by 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.

Image

Abstract members can be declared only in abstract classes, which we'll look at in the next section. Four type of member can be declared as abstract:

  • Methods
  • Properties
  • Events
  • Indexers

Other important facts about abstract members are the following:

  • Abstract members, although they must be overridden by a corresponding member in a derived class, cannot use the virtual modifier in addition to the abstract modifier.
  • As with virtual members, the implementation of an abstract member in a derived class must specify the override modifier.

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
Properties
Events
Indexers
Methods
Properties
Events
Indexers

Abstract Classes

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

  • You cannot create instances of an abstract class.
  • An abstract class is declared using the abstract modifier.

Image

  • An abstract class can contain abstract members or regular, nonabstract members. 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.

Image

This code produces the following output:


I am AbClass
I am DerivedClass

Another Example of an Abstract Class

The following code shows the declaration of an abstract class that contains data members as well as function members. Data members cannot be declared as abstract.

   abstract class MyBase     // Combination of abstract and non-abstract members
   {
      public int SideLength        = 10;             // Data member
      const  int TriangleSideCount = 3;              // Data member

      abstract public void PrintStuff( string s );   // Abstract method
      abstract public int  MyInt { get; set; }       // Abstract property

      public int PerimeterLength( )                  // Regular, non-abstract method
      { return TriangleSideCount * SideLength; }
   }

   class MyClass : MyBase
   {
      public override void PrintStuff( string s )    // Override abstract method
      { Console.WriteLine( s ); }

      private int _myInt;
      public override int MyInt                      // Override abstract property
      {
         get { return _myInt; }
         set { _myInt = value; }
      }
   }

class Program
   {
      static void Main( string[] args )
      {
         MyClass mc = new MyClass( );
         mc.PrintStuff( "This is a string." );
         mc.MyInt = 28;
         Console.WriteLine( mc.MyInt );
         Console.WriteLine( "Perimeter Length: {0}", mc.PerimeterLength( ) );
      }
   }

This code produces the following output:


This is a string.
28
Perimeter Length: 30

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 object. The opposite is true of a sealed class.

  • A sealed class can be instantiated only as a stand-alone class object—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.

Image

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 and values.

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 it cannot have an instance constructor, since you cannot create an instance of the class.
  • Static classes are implicitly sealed. That is, you cannot inherit from a static class.

You access the members of a static class 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:

Image

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 introduced in 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. The code 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.

Image

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.

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

Extension methods allow you to use the second form, even though the first form would be the normal way of writing the invocation.

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.

Image

The important requirements for an extension method are the following:

  • The class in which the extension method is declared must also be declared static.
  • The extension method itself must 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-22 illustrates the structure of an extension method.

Image

Figure 7-22. 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-22 illustrates the code. Classes MyData and ExtendMyData together act like the desired class, with three methods.

Image

This code produces the following output:


Sum:     12
Average: 4

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

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