21.3. A Tale of Two Cats¶
Let’s revisit our Cat
and HouseCat
friends.
Check out this repository for the code for the lesson.
In Visual Studio, open the solution and examine the two classes inside the Cats
project.
21.3.1. Inheriting Fields and Properties¶
Notice that Cat
has a property called Family
, representing
the biological family of all cats.
10 | public string Family { get; } = "Felidae";
|
The family
field is not directly accessible by HouseCat
and can only be accessed by the get
accessor of the Family
property.
However, there is no set
accessor for Family
, so the value of the family
field may only be changed within Cat
.
It makes sense that a subclass should not be able to change the biological family of a cat, since this property should rarely, if ever, change.
Methods of the base class Cat
may be called on instances of the
subclass HouseCat
as if they were defined as part of the
HouseCat
.
Try it out. In your project, open up Program.cs
. Within the Main
method,
create an instance of HouseCat
and call some of the methods it inherits from Cat
.
1 2 3 | HouseCat garfield = new HouseCat("Garfield", 12.0);
garfield.Eat();
Console.WriteLine(garfield.Tired); // prints true
|
The Eat
method was defined in Cat
, but may be called on all
HouseCat
instances as well. We say: “HouseCat
inherits the
method Eat()
from Cat
.” We know we have successfully called Eat()
on
garfield
because the printed statement indicates the cat is now tired.
21.3.2. base
¶
We mention above that a subclass inherits all non-constructor
methods from its base class. Indeed, when extending a class, we will not
be able to create new instances of our subclass HouseCat
using any
constructors provided by Cat
.
The base class Cat
has a constructor that takes a single parameter
of type double
.
12 13 14 15 | public Cat(double weight)
{
Weight = weight;
}
|
But because HouseCat
does not have such a constructor, the following code
does not compile:
HouseCat mittens = new HouseCat(8.4);
Cat
constructors are not inherited by HouseCat
. If we want
to use a Cat
constructor in this subclass, we must explicitly
provide it.
To do so, look at the constructor included in HouseCat
:
11 12 13 14 | public HouseCat(string name, double weight) : base(weight)
{
Name = name;
}
|
Here, we use the :
syntax again with the base
keyword on the constructor signature.
This combination allows the constructor to extend the base class constructor that takes a weight
parameter.
In this case, the subclass constructor also sets the value of the name
field.
The call to the base class constructor must be on the subclass’s constructor signature.
If a base class constructor takes no arguments, then the no-argument constructor is implicitly called for you in the subclass. A no-argument, or no-arg constructor, is just as the name implies, a constructor that takes no arguments.
For example, we can add an additional constructor in Cat
:
17 18 19 20 | public Cat()
{
Weight = 13.0;
}
|
Then in HouseCat
, we can simply define another constructor as this:
16 17 18 19 | public HouseCat(string name)
{
Name = name;
}
|
Even though we don’t explicitly specify that we want to call a
constructor from Cat
, the no-argument constructor will be called. Now, we can
initialize a new HouseCat
with only a name field and the Cat
no-argument
constructor will still be applied. Back in Program.cs
, you can confirm that the base
class constructor has been called:
1 2 | HouseCat spike = new HouseCat("Spike");
Console.WriteLine(spike.Weight); // prints 13
|
As a consequence of this constructor syntax, we can easily expose any
constructor from the base class by providing a subclass constructor that has the same signature, no method body, and calls the base class constructor with : base
.
1 2 3 4 | public HouseCat(double weight) : base(weight)
{
// This is all there is to this constructor!
}
|
Warning
This constructor is a bad one, and is included merely to introduce
syntax and usage. We would not want to have a constructor for
HouseCat
that didn’t initialize an essential field such as name
.
21.3.3. override
¶
Sometimes when extending a class, we’ll want to modify behavior provided by the base class. This can be done by replacing the implementation of an inherited method by a completely new method implementation. For a given method, we can do this via method overriding.
In our example, the Noise
method of HouseCat
overrides the
method of the same name in Cat
. When we override it, we should use override
in the signature of the method in the subclass and virtual
in the signature of the base class.
Here are the methods in question.
In Cat
:
38 39 40 41 | public virtual string Noise()
{
return "Meow!";
}
|
In HouseCat
:
26 27 28 29 | public override string Noise()
{
return "Hello, my name is " + Name + "!";
}
|
If we have a HouseCat
object and call its Noise()
method, we will be
using the method defined in HouseCat
.
1 2 3 4 5 | Cat plainCat = new Cat(8.6);
HouseCat cheshireCat = new HouseCat("Cheshire Cat", 26.0);
Console.WriteLine(plainCat.Noise()); // prints "Meow!"
Console.WriteLine(cheshireCat.Noise()); // prints "Hello, my name is Cheshire Cat!"
|
Warning
When overriding a method from a base class, the method name, access level, type and number of parameters, and return type must be exactly the same.
In this example, the parts of our method that we have to match are:
public string Noise();
When overriding a method, we may call the method from the base class
that we are overriding by using base
. Modify your HouseCat.Noise()
method as follows:
38 39 40 41 42 43 44 45 46 47 48 | public override string Noise()
{
if (IsSatisfied())
{
return "Hello, my name is " + Name + "!";
}
else
{
return base.Noise(); // prints "Meow!"
}
}
|
This calls the overridden method in the base class via
base.Noise()
, carrying out the original behavior if the given
conditional branch is reached.
21.3.4. Object
Class¶
In a previous lesson, we introduced the special methods: Equals
and
ToString
. All classes contain default implementations of these methods that
can be overridden.
In fact, these default methods are part of a class called Object
.
All classes we create in C# have access to the methods and members of the Object
class, because it is the base class in all .NET class hierarchies.
In the case of Cat
and HouseCat
, Cat
implicitly extends Object
.
Since Cat
is a subclass of the Object
class and HouseCat
is a subclass of the Cat
class, HouseCat
and Cat
can both access different methods and members of the Object
class.
So the default implementations of Equals
and ToString
(along with a few other
methods)
are made available to us via inheritance.
Note that we should use the override
keyword when we provide new
implementations of these methods as well.
21.3.5. Check Your Understanding¶
Question
For this question, refer to the code block below.
1 2 3 4 5 6 7 8 9 10 11 | public class Message
{
public bool Friendly { get; } = true;
public string Language { get; }
public string Text { get; }
public Message(string language, string text) {
Language = language;
Text = text;
}
}
|
A class called Greeting
extends Message
. Greeting
and
Message
are both defined within a package called Speech
.
Select all of the fields, properties, and methods that are inherited by
Greeting
.
Friendly
Language
Text
Message
friendly
language
text
Question
For this question, refer to the code block below.
1 2 3 4 5 6 7 8 9 10 11 | public class Message
{
public bool Friendly { get; } = true;
public string Language { get; }
public string Text { get; }
public Message(string language, string text) {
Language = language;
Text = text;
}
}
|
A class called Greeting
extends Message
. What would a constructor for
Greeting
need to be to call the Message
constructor?
public Greeting(string language, string text, bool friendly) { super(language, text); Friendly = friendly; }
public Greeting(string language, string text) : base(language, text) { }
public Greeting() : base(language, text) { }
public Greeting(string language, string text) { Language = language; Text = text; }