Chapter 8. Inheritance

After completing this chapter, you will be able to:

  • Describe the importance of inheritance in object-oriented programming.

  • Define a base class.

  • Define a derived class.

  • Access base-class members from the derived class.

  • Use the virtual keyword to achieve polymorphism.

  • Define abstract classes and abstract methods.

  • Define sealed classes.

  • Use interfaces.

In this chapter, you will learn how to use all aspects of inheritance in C++/CLI. You will see how to define base classes and derived classes, and you will find out how to use these classes effectively in your application.

What is inheritance?

Inheritance is an important concept in object-oriented programming, helping us relate and classify types in a way that makes our applications more type-safe, flexible, and extensible.

Note

Type-safe means that the type system makes it easy to use the correct type in the correct place, and it’s easy for the compiler to spot any mistakes that you make.

As an example, consider cars, trucks, and buses. All of these are types of vehicles: we can say that a car “is a” vehicle and that a sports car “is a” car. We tend to classify the world in terms of more general and more specific types all the time: A manager is also an employee; a savings account is an account; and so on.

How we view things depends on the job we need to do. If I just need to drive down the block, I could use any kind of car; for example, a sports car would do, as would an SUV—as long as it is a car. But, if I need to take my family to the airport, a sports car won’t do: I need to be more specific.

Inheritance lets you use this classification mechanism in your code. If I am writing an application to monitor traffic flow, I might have a function to count the number of vehicles passing a given point. Using inheritance, the compiler knows that cars, trucks, and buses are all vehicles, so I can pass all of those to the function.

The advantages of inheritance are well documented, resulting in better-structured code that is easier to work with and maintain.

Inheritance terminology

When you use inheritance you are dealing with an “is a” relationship between a parent class and one or more child classes. You will find that there are several terms used to describe this relationship, including the following:

  • C++ tends to use the term base and derived classes.

  • Java uses superclass and subclass.

  • Other languages might use parent and child.

Using the correct terms for your language is, of course, not as important as getting the relationships correct.

Inheritance and code reuse

Suppose that you are designing a Vehicle class and some classes that derive from it. You will put the things that are common to all vehicles in the Vehicle class, using the derived classes to implement those features that make them unique.

The derived classes inherit the functionality of the Vehicle class. They have to; otherwise, they would not be Vehicles. This means that after you have implemented functionality in the Vehicle class, you don’t have to duplicate it in the derived classes.

It is very important to understand that code reuse is not the main reason for inheritance. Although it is useful, the main reason why you want to use inheritance is to define relationships between types. If you happen to also gain the benefit of code reuse, this is a bonus. If you use inheritance solely for code reuse, you risk building incorrect inheritance models.

Designing an inheritance hierarchy

Before you start writing any code to use inheritance in C++, you should spend some time designing the inheritance hierarchy. Identify classes that have common behavior, and consider whether these classes would benefit from using inheritance.

In this chapter, you will define and implement an inheritance hierarchy representing different types of bank accounts. The following illustration shows how the classes will be arranged in the inheritance hierarchy:

A diagram showing the relationship between BankAccount, the base class, and CurrentAccount and SavingsAccount, the derived classes.

Note

This illustration uses Unified Modeling Language (UML) notation to represent inheritance. Each box in this diagram is a class. The arrow pointing to BankAccount denotes inheritance in UML.

BankAccount is the base class. It defines common data members and member functions that are common to all kinds of bank accounts.

CurrentAccount and SavingsAccount are derived classes, representing specific types of bank account. These derived classes inherit all the data members and member functions from BankAccount, and they can add extra data members and member functions, as required.

CurrentAccount and SavingsAccount can also override member functions defined in BankAccount. For example, the BankAccount class might have a method named CanDebit to indicate whether a certain amount of money can be debited from the account. The policy rules for allowing debits are different for each type of account; therefore, CurrentAccount and SavingsAccount can override the CanDebit method to perform the required processing for each type of account.

You will define and implement all three of these classes during this chapter. Let’s begin with the base class, BankAccount.

A word on substitutability

Substitutability means that everywhere you want a base class object, you can use a derived class object. For example, if I ask you to bring me a vehicle (base class), a car or a truck (derived class) will suffice because I wasn’t specific. I expect, however, that anything you bring me is a vehicle, and as a minimum does everything that a vehicle can do.

For this reason, derived classes can add functionality over and above their base class, and can redefine operations that they inherit, but they are not allowed to remove functionality.

You can regard the functionality provided by the base class as a contract that the derived class must honor. If it doesn’t, it is not substitutable for the base class, and the inheritance relationship is not proper.

Defining a base class

When you define a base class, you can start it by defining the common member functions that will be required by all the derived classes. After you have defined these member functions, add data members to support their implementation. Then, provide one or more constructors to initialize these data members.

Tip

Always start by deciding what it is that a class must do, and then think about what data members are needed to support these operations.

In this exercise, you will create a new application and define the BankAccount class. The BankAccount class will be the base class for all types of bank accounts in the application.

In BankAccount, you will define the common member functions and data members that apply for all types of bank accounts. You will also define a constructor and destructor for this class.

  1. Start Visual Studio 2012 and create a new CLR Console Application project named BigBank.

  2. On the Project menu, click Add New Item.

  3. In the Add New Item dialog box, in the pane on the left, select Visual C++, and then, in the center pane, click Header File (.h).

  4. Toward the bottom of the dialog box, in the Name box, type BankAccount.h, and then click Add.

    Note

    Another way that you can open the Add New Item dialog box is to right-click the project name in Solution Explorer and then, in the shortcut menu that appears, point to Add, and then click New Item.

  5. Define the BankAccount class as follows:

    #pragma once
    
    using namespace System;
    
    ref class BankAccount
    {
    public:
        BankAccount(String ^holder);
        void Credit(double amount);
        void Debit(double amount);
        double GetBalance();
    private:
        String ^accountHolder;
        double balance;
    };

    Tip

    The #pragma once compiler directive specifies that this header file will be processed only once by the compiler during a build. This directive is particularly useful for frequently included header files, such as those containing base-class definitions.

    If you omit the #pragma once directive, you will almost certainly get a compiler error when you try to build the application later on because BankAccount.h will be included in several different places in the application, and the compiler will generate an error if it sees the BankAccount class declaration more than once.

  6. Repeat steps 2 through 4, but this time add a new C++ source file named BankAccount.cpp to the project.

  7. Type the following code in the source file to implement the BankAccount class:

    #include "stdafx.h"
    #include "BankAccount.h"
    
    BankAccount::BankAccount(String ^holder)
    : accountHolder(holder), balance(0.0)
    {
    }
    
    void BankAccount::Credit(double amount)
    {
        balance += amount;
    }
    
    void BankAccount::Debit(double amount)
    {
        balance -= amount;
    }
    
    double BankAccount::GetBalance()
    {
        return balance;
    }

    Note

    The constructor uses a member initialization list to initialize the BankAccount data members, which is the preferred syntax for initializing data members in a constructor. Furthermore, it’s the only way to invoke base-class constructors, which will become apparent when you define the CurrentAccount and SavingsAccount classes shortly.

  8. Build the application and fix any compiler errors.

Defining a derived class

To define a derived class in C++/CLI, use the following syntax:

ref class MyDerivedClass : MyBaseClass
{
    ...
};

The colon in the class definition indicates inheritance. Following the colon, you specify the name of the base class.

Note

In standard C++ you would put one of the keywords public, protected, or private after the colon and before the base class name. C++/CLI (and all other Microsoft .NET languages) only support public inheritance, so you do not need to use the public keyword. It is not an error if you use it, but be aware that it is not required.

In this exercise, you will define and implement the CurrentAccount and SavingsAccount classes. CurrentAccount will inherit from BankAccount, which means that there is no need to reimplement inherited member functions such as Credit and Debit. Likewise, there is no need to redefine inherited data members such as accountHolder and balance. All you need to define in CurrentAccount are additional member functions and data members, which apply specifically to current accounts.

SavingsAccount will have an interest rate associated with it. Because the interest rate is common to all SavingsAccount objects, it makes sense to make it a static member of the class.

  1. Continue using the project from the previous exercise.

  2. Add a new header file to the project named CurrentAccount.h.

  3. Type the following code in the header file to define the CurrentAccount class:

    #pragma once
    
    #include "BankAccount.h"
    
    ref class CurrentAccount : BankAccount
    {
    public:
        CurrentAccount(String ^holder, double limit);
        void ChangeOverdraftLimit(double newLimit);
        double GetOverdraftLimit();
    private:
        double overdraftLimit;
    };

    Notice the #include “BankAccount.h” directive. This directive is required because BankAccount is the base class of CurrentAccount. The compiler needs to know how BankAccount is defined to compile the CurrentAccount class.

    Also notice that the CurrentAccount constructor takes two parameters; the first parameter initializes the account holder’s name (defined in BankAccount), and the second initializes the overdraftLimit (defined in CurrentAccount).

  4. Add a new C++ source file to the project named CurrentAccount.cpp.

  5. Type the following code in the source file to implement the CurrentAccount class:

    #include "stdafx.h"
    #include "CurrentAccount.h"
    
    CurrentAccount::CurrentAccount(String ^holder, double limit)
        : BankAccount(holder), overdraftLimit(limit)
    {
    }
    
    void CurrentAccount::ChangeOverdraftLimit(double newLimit)
    {
        overdraftLimit = newLimit;
    }
    
    double CurrentAccount::GetOverdraftLimit()
    {
        return overdraftLimit;
    }

    The most important thing to note about this code is the CurrentAccount constructor. The member initialization list includes the syntax BankAccount(holder). This calls the constructor in the base class, BankAccount, to initialize inherited data members. If you take a look in BankAccount.cpp, you’ll see that the BankAccount constructor requires a String^ parameter to set the account holder’s name. The balance is always set to 0 initially.

    Note

    The derived-class constructor must call the base-class constructor by using the member initialization list syntax. If you forget to call the base-class constructor, the compiler will attempt to call a no-argument constructor in the base class on your behalf; if there isn’t a no-argument constructor in the base class, you’ll get a compiler error.

  6. Add a header file to the project named SavingsAccount.h.

  7. Add the following declaration for the SavingsAccount class to the file:

    #pragma once
    
    #include "BankAccount.h"
    
    ref class SavingsAccount : BankAccount
    {
    public:
         SavingsAccount(String ^holder);
         static void SetInterestRate(double rate);
         static double GetInterestRate();
    
    private:
         static double interestRate;
    };
  8. Add a source file to the project named SavingsAccount.cpp.

  9. Add the following code to the file to implement the SavingsAccount class:

    #include "stdafx.h"
    
    #include "SavingsAccount.h"
    
    SavingsAccount::SavingsAccount(String ^holder) : BankAccount(holder) { }
    
    void SavingsAccount::SetInterestRate(double rate)
    {
         interestRate = rate;
    }
    
    double SavingsAccount::GetInterestRate()
    {
         return interestRate;
    }
  10. Build the application and fix any compiler errors.

Creating derived class objects

In this exercise, you will see how to create and use objects of a derived class.

  1. Continue using the project from the previous exercise.

  2. Open BigBank.cpp and add #includes for the CurrentAccount and SavingsAccount header files.

    #include "CurrentAccount.h"
    #include "SavingsAccount.h"

    Note

    There is no need to explicitly write #include “BankAccount.h” because this header file is already included in CurrentAccount.h and SavingsAccount.h.

  3. Delete the “Hello World” line from main. Add code to create a CurrentAccount object and exercise it.

    CurrentAccount acc("Me", 2000.0);
    acc.Credit(100.0);
    
    double balance = acc.GetBalance();
    double overdraft = acc.GetOverdraftLimit();
    
    Console::WriteLine("Balance: {0}", balance);
    Console::WriteLine("Overdraft: {0}", overdraft);

    You can see that the CurrentAccount object gives you access to the Credit and GetBalance member functions from Account. It also gives you access to its own GetOverdraftLimit function.

  4. Add code to main to create a SavingsAccount.

    SavingsAccount::SetInterestRate(2.5);
    SavingsAccount sacc("You");
    double rate = sacc.GetInterestRate();
    
    Console::WriteLine("Interest rate: {0}", rate);
  5. Build and run the application.

    You should see the interest rate printed, showing that you can access a static member through either the class name or through an object.

  6. Build and run the application.

    You should see output similar to this:

    Balance: 100
    Overdraft: 2000
    Interest Rate: 2.5

Concrete and abstract classes

When you define an inheritance hierarchy, the base class acts as a repository for the common member functions and data members required by derived classes. However, the base class often doesn’t represent a real object.

Consider the bank account example we’ve been developing in this chapter. When you walk into a bank to open an account, you have to specify what type of account you want (checking account or savings account). You can’t just open a “bank account.”

In similar fashion, when programming, you should prevent generic BankAccount objects from being created. You should allow only derived classes such as CurrentAccount and SavingsAccount to be instantiated. To accomplish this in C++/CLI, declare the BankAccount class as an abstract class, as demonstrated in the following:

ref class BankAccount abstract
{
    // ... Class body, as before
};

Observe how the abstract modifier appears after the class name.

In this exercise, you will modify the BankAccount class as just described to make it an abstract class. You will then write some code in the main function in the application to create and use CurrentAccount and SavingsAccount objects.

  1. Continue using the project from the previous exercise.

  2. Open BankAccount.h and change the BankAccount class definition by adding the abstract keyword.

    ref class BankAccount abstract
    {
      ...
    };
  3. Open BigBank.cpp to edit the main function for the application.

  4. Inside the main function, try to create a BankAccount object as follows:

    BankAccount genericAccount("Fred");

    IntelliSense flags an error, which confirms the fact that you cannot create instances of an abstract class.

  5. Delete the statement you created in Step 4.

Overriding member functions

When you define a base class, you must consider whether derived classes will need to override any of your base-class member functions. For each member function in the base class, there are three possibilities:

  • The base-class function is suitable for all derived classes. Derived classes will never need to override the member function with customized behavior. The Credit and GetBalance member functions in BankAccount fit this scenario. These functions will work the same way for all derived classes. Here’s an example:

    ref class BankAccount abstract
    {
    public:
        void Credit(double amount);  // This function cannot be overridden
        double GetBalance();         // Neither can this one
        ...
    };
  • The base-class function performs some task, but derived classes might need to override the function to provide customized behavior. To make it possible to override a base-class function, you must declare the function by using the virtual keyword in the base-class definition, as shown in this example:

    ref class BankAccount abstract
    {
    public:
        virtual String ^ToString() override;  // This function can be overridden
        ...
    };

    This function declaration uses both the virtual and override keywords. We have seen how virtual indicates that a derived class can override this function. The override keyword must be used when you are overriding a function from a base class; in this case, ToString is defined in the ultimate base class, System::Object, and so we use override to show that we are intending to override this function and haven’t just added a function that looks exactly the same.

    If by some chance you want to add a function that looks like a base class function but does not override it, you would use the new modifier:

    // This function does not override ToString
    virtual String ^ToString() new;
  • The base-class function specifies some operation that is required by all derived classes, but each derived class needs to perform the operation in a significantly different way. There is no sensible common behavior you can define in the base class. To do this, you declare the base-class member function as abstract. C++ calls these pure virtual functions.

    There are two ways to denote a pure virtual function. The first comes from standard C++ and involves putting “= 0” at the end of the function declaration. The second way, introduced by C++/CLI, is to add the abstract keyword. Here’s an example:

    ref class BankAccount abstract
    {
    public:
        // Declare a pure virtual function using standard C++ syntax
        virtual void Debit(double amount) = 0;
    
        // Declare a pure virtual function using C++/CLI syntax
        virtual void Debit(double amount) abstract;
        ...
    };

Note

Including a pure virtual function in a class means that it must be abstract, although the opposite is not necessarily true: a class can be abstract without having any pure virtual functions. If a derived class does not implement the function, it too must be abstract.

In this exercise, you will define a ToString member function in the BankAccount class. You will declare this function as virtual to give derived classes the opportunity to override the function if they want to. You will also modify the way in which debits are handled so that derived classes decide whether a withdrawal can be made.

  1. Continue using the project from the previous exercise.

  2. Open BankAccount.h and add the following public function declarations to the BankAccount class:

    // Derived classes can override this function
    virtual String ^ToString() override;
    // Derived classes must override this function
    // You can use '=0' instead of 'abstract'
    virtual bool CanDebit(double amount) abstract;
  3. Open BankAccount.cpp and implement the ToString function as follows:

    String ^BankAccount::ToString()
    {
        String ^result = gcnew String("Account holder: ");
        result = String::Concat(result, accountHolder);
        result = String::Concat(result, ", Balance: ");
        result = String::Concat(result, balance.ToString());
        return result;
    }

    Observe the use of the String::Concat function, which is used for joining strings together.

  4. Modify the Debit member function as follows:

    bool BankAccount::Debit(double amount)
    {
        if (CanDebit(amount))
        {
            balance -= amount;
            return true;
        }
        else
        {
            return false;
        }
    }

    Notice that Debit now calls CanDebit to verify that the debit is allowed. CanDebit isn’t implemented in BankAccount, but all derived classes are obliged to provide this function. At run time, the correct version of CanDebit is called depending on the type of bank account being used for the debit operation—polymorphism in action! We have also changed the return type of Debit so that calling code can determine whether the debit worked.

  5. Change the prototype for Debit in BankAccount.h so that it returns a bool.

  6. Open CurrentAccount.h and add the following public function declarations to the Current Account class:

    // Choose to override ToString
    virtual String ^ToString() override;
    // Have to override CanDebit
    virtual bool CanDebit(double amount) override;

    Notice the use of the override keyword. This instructs the compiler that you are intending to override a function from the base class and haven’t just added a function that happens to look exactly the same.

  7. Open CurrentAccount.cpp and implement the ToString function as follows:

    String ^CurrentAccount::ToString()
    {
        String ^result = BankAccount::ToString();
        result = String::Concat(result, ", Overdraft Limit: ");
        result = String::Concat(result, overdraftLimit.ToString());
        return result;
    }

    The BankAccount::ToString() syntax calls the ToString function in the base class (BankAccount). This call returns a string containing the account holder’s name and balance. We concatenate the overdraftLimit value to this string and return it.

  8. Still in CurrentAccount.cpp, implement the CanDebit function as follows:

    bool CurrentAccount::CanDebit(double amount)
    {
        return (amount <= GetBalance() + overdraftLimit);
    }

    There are two things to note about this code. First, we need to call the GetBalance function to get the current balance. Just because we inherit from BankAccount doesn’t mean that we can access its private balance member.

    Second, notice the way in which the return statement is written, returning the result of the expression directly. We could have used an if statement to check the condition and return true or false, but this code is shorter while being no less readable, and that is something that C++ coders like.

  9. Open SavingsAccount.h and add the following public function declaration to the Savings Account class:

    virtual bool CanDebit(double amount) override;

    You are obliged to override CanDebit because it’s a pure virtual function. However, you do not have to override ToString, because the base class (BankAccount) provides a default implementation of this function. The SavingsAccount class chooses not to override ToString.

  10. Open SavingsAccount.cpp and implement the CanDebit function as follows:

    bool SavingsAccount::CanDebit(double amount)
    {
        return (amount <= GetBalance() / 10);
    }

    This function makes it possible for the user to withdraw one-tenth of the current balance.

  11. Open BigBank.cpp and replace the existing code in the main function with the following:

    Console::WriteLine("Testing the CurrentAccount");
    CurrentAccount current("Jane", 100);
    current.Credit(500);
    
    // Should be accepted
    if (current.Debit(550) == true)
    {
         Console::WriteLine("Debit(550) OK");
    }
    else
    {
         Console::WriteLine("Debit(550) failed");
    }
    
    // Should be declined
    if (current.Debit(100) == true)
    {
         Console::WriteLine("Debit(100) OK");
    }
    else
    {
         Console::WriteLine("Debit(100) failed");
    }
    
    Console::WriteLine(current.ToString());
    
    Console::WriteLine("
    Testing the SavingsAccount");
    SavingsAccount savings("Fred");
    savings.Credit(500);
    
    // Should be accepted
    if (savings.Debit(50) == true)
    {
         Console::WriteLine("Debit(50) OK");
    }
    else
    {
         Console::WriteLine("Debit(50) failed");
    }
    
    // Should be declined
    if (savings.Debit(46) == true)
    {
         Console::WriteLine("Debit(46) OK");
    }
    else
    {
         Console::WriteLine("Debit(46) failed");
    }
    
    Console::WriteLine(savings.ToString());
    
    return 0;
  12. Build and run the application. Check that the output is what you expect.

  13. Create a breakpoint on the first statement in the main function and then start the application in the debugger. Step through the application one statement at a time, stepping into each function to see which version is called during execution.

Protected access

You have used two access levels for class members so far: private and public. You know that private members cannot be used outside the defining class, whereas public members can be used by anyone. Inheritance, however, introduces a relationship between two classes, and there is a need for an access level that grants access to derived classes. The protected access level is less restrictive than private but more restrictive than public.

Any members that are defined as protected can be used in the base class, and in any class that derives from it.

Tip

You should only make member functions protected, not data members. The data belonging to a class is the responsibility of that class, and it should not allow direct modification by derived classes.

In this example, you will add a protected member function to the BankAccount class. Suppose that BankAccount has a RoutingInstructions function that details how a given size of debit or credit should be handled for a particular account. This function is not to be accessed by users of the class but might be of use to derived classes.

  1. Continue using the project from the previous exercise.

  2. Open BankAccount.h and add the following protected function declaration to the BankAccount class:

    protected:
        String ^RoutingInstructions(double amount);

    Note

    The order in which you specify the public, private, and protected sections of a class declaration does not matter, although many people will put the public section first because that is the most important section from the point of view of users of the class.

  3. Open BankAccount.cpp and add the definition of RoutingInstructions.

    String ^BankAccount::RoutingInstructions(double amount)
    {
        return "Some string...";
    }
  4. Open CurrentAccount.cpp and modify the CanDebit function so that it calls RoutingInstructions. You should not see any warnings from the compiler, because CurrentAccount is allowed to call this function.

    bool CurrentAccount::CanDebit(double amount)
    {
        String ^details = RoutingInstructions(amount);
        return (amount <= GetBalance() + overdraftLimit);
    }
  5. Open BigBank.cpp and try adding a call to RoutingInstructions on either the SavingsAccount or CurrentAccount objects.

    IntelliSense flags an error because you are not allowed to call this function from an unrelated class. If you build the project, you will get error C3767 (‘BankAccount::RoutingInstructions’: candidate function(s) not accessible)

Defining sealed classes

In C++/CLI, you can define a class as sealed, which means that the class cannot be used as a base class. Defining a class as sealed is useful if it performs operations that you don’t want customized in derived classes, but it is also useful in another, less obvious way. If a class is sealed, the compiler knows that it will not have any derived classes. Because this means that there will be no calls to virtual functions, the compiler might be able to generate more efficient code.

To mark a class as sealed, use the sealed keyword in the class definition as follows:

ref class MyClass sealed
{
    // ... Class body, as before
};

Abstract and sealed

It might appear at first sight that abstract and sealed are opposites: one means that a class has to have derived classes to be useful, whereas the other means that you can’t derive classes. It is, however, possible to use abstract and sealed together on a class.

Suppose that you have a class that only contains static (class-level) members. It would make sense to say that you do not want objects of this type, because there are no object-level functions. Making the class abstract prevents instantiation. In addition, you might want to prevent anyone from adding extra functions to your class, which sealed does.

Note

You can specify the sealed and abstract modifiers in any order.

Defining and using interfaces

Interfaces are an important programming construct in .NET; therefore, you need to be able to use them in C++/CLI. You have already learned about pure virtual functions, which are specified in a base class but implemented by a derived class. Imagine that you have a class that only contains pure virtual functions, such as the following:

ref class XmlWriter
{
public:
    virtual void ReadFromXmlFile(String ^filename) = 0;
    virtual void WriteToXmlFile(String ^filename) = 0;
};

This class specifies how to convert data to and from XML, and it is implemented by derived classes to suit their particular data. You could use the following:

ref class MyData : XmlWriter
{
public:
    void ReadFromXmlFile(String ^filename) override
    {
        // Read my data
    }

    void WriteToXmlFile(String ^filename) override
    {
        // Write my data
    }
};

An interface is similar, and you could rewrite the XmlWriter class as follows:

interface class IXmlWriter
{
    void ReadFromXmlFile(String ^filename);
    void WriteToXmlFile(String ^filename);
};

The definition of the derived class also needs to be changed. You need to specify the interface name, and declare the functions as virtual.

ref class MyData : IXmlWriter
{
public:
    virtual void ReadFromXmlFile(String ^filename)
    {
        // Read my data
    }

    virtual void WriteToXmlFile(String ^filename)
    {
        // Write my data
    }
};

You can see that you inherit from an interface in the same way that you inherit from a class. However, there are a number of differences between a class and an interface:

  • A class can contain implementation and data members; an interface cannot.

  • All members of an interface are public and abstract by definition.

  • Interface names should start with an “I” (capital i) by convention.

  • A class can only inherit from one base class, but it can implement as many interfaces as it wants.

If you have used standard C++, you might know that a class can inherit from many base classes. This feature is called multiple inheritance. .NET only allows you to inherit from one base class, but because you can implement as many interfaces as necessary, you can get the benefits of multiple inheritance.

Interfaces are very important in .NET, not least of which because they are cross-language. You can define an interface in C++ and implement it in C#. More important than that, they provide a way to specify a contract, which one class implements, and another uses. Neither class might have knowledge of the other, but they can communicate because they both know about and use the interface contract. You will see many examples of interfaces as you progress through the rest of the book.

Quick reference

To

Do this

Define an abstract base class.

Use the abstract keyword in the class definition. For example:

ref class MyBase abstract
{
...
};

Define a derived class.

In the derived-class definition, use a colon followed by the name of the base class. For example:

ref class MyDerived : MyBase
{
    ...
};

Construct derived objects.

In the derived-class constructor, use a member initialization list to call the base-class constructor. For example:

MyDerived::MyDerived(int bdata, int ddata)
    : MyBase(bdata), derivedData(ddata)
{
    ...
}

Enable derived classes to access members in the base class while denying access to unrelated classes.

Declare the members as protected in the base class. For example:

ref class MyBase abstract
{
protected:
    void functionVisibleToDerivedClass;
    ...
};

Define overridable member functions in the base class.

Declare the member functions as virtual in the base class. For example:

ref class MyBase abstract
{
protected:
    virtual void myOverridableFunction();
    ...
};

Specify base-class member functions that must be overridden by derived classes.

Declare the member functions as virtual in the base class. After the closing parenthesis, append = 0 or abstract. For example:

ref class MyBase abstract
{
protected:
    virtual void myMustBeOverridden() = 0;
    ...
};

To prevent a class being used as a base class.

Use the sealed keyword in the class definition. For example:

ref class MySealedClass sealed
{
    ...
};

Define an interface.

Use the interface class keyword, and start the interface name with “I”. Do not provide any bodies for the functions you define. For example:

interface class IMyInterface {
    void function1(int n);
    int function2(double d);
};

Implement an interface.

Use the same syntax as for inheritance. Implement all the required functions in your class. For example:

ref class MyImplementingClass
        : IMyInterface
{ public:
    void function1(int n);
    int function2(double d);

    // Other members, as needed
    ...
};
..................Content has been hidden....................

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