From our discussion of inheritance in Chapter 5, we learned that
Inheritance is used to derive a new class definition from an existing class when the new class is perceived to be a special case of the existing class.
The derived class automatically inherits the data structure and behaviors of the base class.
C# uses a colon followed by a class name in a class declaration to signal that one class is derived from another:
public class Person { string name; public string Name { get { return name; } set { name = value; } } } // We derive the Student class from Person. public class Student : Person { // If we define nothing in the body of this class, it will still have // one field -- name -- and one property -- Name -- because these are // inherited from Person. (Actually, we have more features than that, // because Person in turn inherits features from Object!) }
Although we mastered the basics of inheritance in that chapter, it turns out that there are a lot of important subtleties about inheritance in C# that we haven't yet discussed; we'll do so now.
By virtue of inheritance, everything defined in a base class is automatically present in a derived class—inheritance is an "all-or-nothing" proposition. However, some inherited features (fields, methods, properties, and so on) may not be directly accessible by the derived class, depending on their access permissions as assigned in the base class.
Consider the following base class:
public class Person { // Field. accessibility_modifier int age; // Other details of this class omitted. }
Accessibility for a feature, as we learned in Chapter 4, can be one of the following:
private
public
protected
internal
protected internal
Unspecified, which for features defaults to private
We learned in Chapter 4 about public and private accessibility; a feature given protected accessibility is in scope and hence publicly available to derived classes but private to everything else. The other two accessibilities, internal and protected internal, are less frequently used and a discussion of them is beyond the scope of this book.
Suppose that we derive the Student class from Person as follows:
public class Student : Person { // The age field is inherited from the base class ... // here, we add a method that directly manipulates this field // by name. public bool IsOver65( ) { if (age > 65) { return true; } else { return false; } } }
What will happen when we try to compile the Student class? The answer to this question depends on how access has been defined for the age field of Person.
If we declared age to be either protected or public in Person, then the age field is both inherited and directly accessible to the Student class by its simple name, and the Student class shown earlier will compile without error.
If, on the other hand, age is declared to be private in Person, then we'll get a compilation error on following line of Student code:
if (age > 65) {
The error message will be
error CS0122: 'Person.age' is inaccessible due to its protection level
That's because the age field is indeed inherited—it's part of the data structure comprising a Student object—but it's nonetheless "invisible" to the Student class! It's analogous to an internal organ in our body: e.g., our heart is part of our physical body, but we can't see or access it directly.
If our inclination is to make all fields private, how can a subclass ever manipulate its privately inherited fields? The answer is quite simple: through the public (or protected) accessor methods or properties that it has also inherited from its parent class. We've revised the previous example program to illustrate this technique.
First, we make sure that the parent Person class provides a public Age property (good programming practice would always call for this anyway):
public class Person { private int age; // details omitted // We provide a property for subclasses to inherit. // (The property could be given protected access if // we ONLY want subclasses to access it.) public int Age { get { return age; } set { age = value; } } }
Then, we use the inherited Age property from within the Student's IsOver65 method, and we're back in business: Student compiles without error.
public class Student : Person { // other details omitted. public bool IsOver65( ) { // Even though the age field per se is inaccessible, // the Age property that we inherit from Person // allows us to access the value of the age attribute. if (Age > 65) return true; else return false; } }
As we mentioned back in Chapter 4, it's generally considered good practice to always use properties to access the values of fields, even from within a class's own methods, so as to take advantage of any special processing that the property accessors might provide relative to that field.
As we learned in Chapter 5, if we provide a method in a derived class whose signature matches that of a base class method (identical method name and parameter list) then we're said to have overridden the base class method. When would we want/need to do this? When the derived class needs to do something slightly more specialized in response to a message than its base class did, as in the following example:
public class Person { private string name; private string ssn; public string Name { // Accessor details omitted. } // etc. for Ssn // Have a Person object describe itself. public virtual string GetDescription() { return Name + " (" + Ssn + ")"; // e.g., "John Doe (123-45-6789)" } } public class Student : Person { private string major; public string Major { // Accessor details omitted. } // We want a Student object to return a description of itself // differently from the way its parent class (Person) does so. // So, we equip this subclass with a method having the exact // same signature as was defined for its parent class; this // version of the method overrides (masks) the inherited version. public override string GetDescription() { return Name + " (" + Ssn + ") [" + Major + "]"; // e.g., "Mary Smith (987-65-4321) [Math]" } }
As we learned in Chapter 5, a derived class method can call a base class version of the same method using the base keyword. This feature allows us to reduce code redundancy because we can reuse the work done by the base class method and then add in whatever additional code the derived class method needs.
Let's streamline the GetDescription method of the Student class such that it first calls the Person class version of the same method.
public class Student : Person { string major; // etc. // Exact same method signature as was defined for Person -- so, this // method overrides (masks) the inherited version. public override string GetDescription() { // Notice, however, that we are now calling the parent class's version // of the method so as to reuse that code. return base.GetDescription() + " [" + Major + "]"; // e.g., "Mary Smith (123-45-6789) [Math]" } }
Just as the this keyword is used to generically refer to an object from within one of its methods, base is used when we want to generically refer to the parent class's version of a feature from within one of its methods.
Another important use of the base keyword has to do with constructors and inheritance, which we'll discuss next.
Constructors aren't inherited in the same manner that methods are. This raises some interesting complications that are best illustrated via an example.
Let's start by declaring a constructor for the Person class that takes two arguments:
public class Person { string name; string ssn; // Public properties Name and Ssn are declared; details omitted. // Only one constructor is explicitly declared. public Person(string n, string s) { Name = n; Ssn = s; } // etc. }
We know from an earlier discussion that the Person class now only recognizes one constructor signature—one that takes two arguments—because the default parameterless constructor for Person has been eliminated.
Now, say that we derive the Student class from Person, and furthermore that we want the Student class to define two constructors—one that takes two arguments and one that takes three arguments. Because constructors aren't inherited, we won't automatically benefit from the fact that the Person class has already gone to the trouble to define a constructor that takes two arguments; we have to recode one explicitly for Student, as follows:
public class Student : Person { string major; // Major property provided; details omitted. // Constructor that takes two arguments. public Student(string n, string s) { // Note the redundancy of logic between this constructor and // the parent constructor -- we'll come back and fix this in a // moment. Name = n; // redundant Ssn = s; // redundant Major = null; } // Constructor that takes three arguments. public Student(string n, string s, string m) { // MORE redundancy! Name = n; // redundant Ssn = s; // redundant Major = m; } // No other constructors are explicitly declared. }
Fortunately, there is a way to reuse a parent class's constructor code without having to duplicate its logic in the derived class's constructor(s). We accomplish this via the same base keyword we discussed a moment ago for the reuse of methods. To explicitly reuse a particular parent class's constructor, we refer to it as : base(optional arguments), and pass in whatever arguments it needs, as the following revised version of the Student class illustrates:
public class Student : Person { string major; // Constructor that takes two arguments. // We'll explicitly invoke the Person constructor with two // arguments, passing in the values of n and s. public Student(string n, string s) : base(n, s) { // We can now concentrate on only those things that need be done // uniquely for a Student. major = null; } // Constructor that takes three arguments. // See above comments. public Student(string n, string s, string m) : base(n, s) { major = m; } }
Similar to the situation in which we used the syntax : this(…) to reuse constructor code within the same class, if a derived class constructor calls a base class constructor, that call is the first operation performed when the derived class constructor is invoked.
Whether we explicitly invoke a base class constructor from a derived class constructor using the : base(…) construct or not, the fact is that C# will always attempt to execute constructors for all of the ancestor classes for a given class, from most general to most specific in the class hierarchy, before launching into a given class's constructor code.
For example, when we create a Student object, we're in reality simultaneously creating an Object, a Person, and a Student, for a Student is all three! So, when we invoke a Student constructor, then an Object constructor will be executed first, followed by a Person constructor, followed by a Student constructor. So, if we were to write a Student constructor without taking advantage of the base(…) syntax, as shown here:
public class Student : Person { string major; // Constructor that takes two arguments. // Here, we're not calling any particular base constructor. public Student(string n, string s) { // We can now concentrate on only those things that need be done // uniquely for a Student. major = null; }
it's as if we've written the following code instead (note bolding):
public class Student : Person { string major; // Constructor that takes two arguments. public Student(string n, string s) : base() { // We can now concentrate on only those things that need be done // uniquely for a Student. major = null; }
thereby explicitly calling the parameterless Person constructor.
This makes intuitive sense, because we said that inheritance represents the "is a" relationship—a Student is a Person, and a Person is an Object—so whatever we have to do when we create an object and a Person will also be required when we create a Student. The question is, which base class constructor will be called if we've defined more than one?
There are several different scenarios that we must explore in answering this question.
We already know that if we derive a class such as Student, but don't bother to define any constructors for the derived class, then C# will attempt to provide us with a default parameterless constructor for that derived class. When we create a new object of the derived class, the parameterless constructor for each of the ancestor class(es) automatically will get called in top-down fashion, with the default constructor of the derived class being called last. The implication of this phenomenon is that if we've derived a class B from class A, and expect to use B's default parameterless constructor, then the base class A must also have a parameterless constructor available for B's constructor to call, either explicitly programmed or by default.
The following example won't compile (we'll explain why in a moment):
public class Person { string name; // A constructor that takes one argument - by having created this, // we've lost Person's default (parameterless) constructor. public Person(string n) { name = n; } // The parameterless constructor is not being replaced in this example. } public class Student : Person { string major; // NO constructors are explicitly defined for Student! So, all // we get for Student is the default parameterless constructor. }
When we try to compile Student, we'll get the following (rather cryptic) error message:
error CS1501: No overload for method 'Person' takes '0' arguments
This is because the C# compiler is trying to create a default constructor that takes no arguments for the Student class but, in order to do so, it knows that it's going to need to be able to call a parameterless constructor for a Person from within the Student default constructor—but, no such constructor for Person exists! The compiler is, in essence, trying to generate the following default constructor for Student:
public Student() : base() { // Initialize a "bare bones" Student -- details omitted. }
The only way around this dilemma is to either
Explicitly program a parameterless constructor for the Person class, to replace the "lost" default Person constructor, for the compiler to take advantage of when creating a default Student class constructor (this is the preferred approach); or
Always use a constructor for the Student class that explicitly invokes a particular version of Person constructor through use of the base keyword.
This latter option is explored in the next case.
This can actually be split into two subcases:
If a base class constructor isn't explicitly called from the derived class constructor via the base : (…) construct, a base class parameterless constructor will still be called, as in Case #1. The following code won't compile, for the same reasons cited earlier:
public class Person { // Details omitted. // A constructor that takes one argument - by having created this, // we've lost Person's default parameterless constructor. public Person(string n) { Name = n; } } public class Student : Person { // Details omitted. // We declare a Student constructor, but don't explicitly invoke a // particular Person constructor from within it. public Student(string n, string m) { Name = n; Major = m; } }
When we try to compile Student, we'll get the compiler error message
No constructor matching Person() found in class Person
Let's repair the problem in Subcase #2A by having the derived class constructor explicitly call a particular parent class constructor that we know to exist via the base(…) construct:
public class Person { string name; // A constructor that takes one argument - by having created this, // we've lost Person's default parameterless constructor. public Person(string n) { name = n; } // No additional constructors are provided for Person. } public class Student : Person { string major; // Constructor. // We'll explicitly invoke the Person constructor with one // argument, passing through the value of n. public Student(string n, string m) : base(n) { major = m; } }
All is well when this code is compiled!