Inheritance and polymorphism are both object-oriented programming concepts. They both involve parent and derived classes, so you need a good overview of classes. We'll first review classes and OOP and then explain concepts of inheritance and polymorphism. They are both more complex concepts but important parts of C# and any OOP program.
Understanding Object Oriented Programming
Object oriented programming involves relationships between classes and the way properties and member methods can be derived from parent classes. OOP divides code into components.
Classes are the main, upper-level components of an OOP program. Classes contain the properties and methods that define a certain component. We reviewed member methods in the last chapter. These class functions make up the verbs for a class. Member variables make up the nouns of the class. These nouns provide the characteristics you see in a class object.
When you design your code, you create classes that represent the components in your code. For instance, suppose you decide to create a zoo application. You want to describe the animals at the zoo. All animals have some basic similarities. Since this is a zoo application, you can consider Animal as the top layer class that describes all the animals at the zoo. Each animal has its own characteristics, but even with these distinct characterizations, they're still animals.
With the main parent Animal class, you can then drill down into the animals themselves. You can have a monkey and a lion class. Both of these classes would derive from the animal class, but they have their own characteristics. They could inherit eyes, nose, skin, and fur but these characteristics would need their own properties. Within the Monkey and Lion class, you derive these main characteristics but you define them individually in your classes.
These concepts are the main ideas behind inheritance. Your classes all inherit from one main class. Put these classes together and you have your zoo application.
Polymorphism is another part of OOP, and we saw some of it in the last chapter. Polymorphism allows your objects to perform differently depending on certain factors. These factors can be input from a user, input from the program, or input from the environment. Polymorphism is done in two ways: overloading or overriding a method. We covered overloading methods in the last chapter, but we'll cover overriding in this chapter. We'll also review overloading again, so you get a full overview of polymorphism and how you can use it in your programs.
Class Inheritance in C#
Now that we have a brief explanation of classes, we can discuss inheritance. We used the Animal class as an example in the previous section. The Monkey class derived from the Animal class, because a monkey is an animal. The same was said of the Lion class. Although a lion is not the same animal as a monkey, they are still both animals. When you work with classes and inheritance, the Animal class is said to be the parent class. The Monkey and Lion classes are said to derive from Animal, and they are considered child classes.
Let's first take a look at an example of an Animal parent class.
public class Animal
{
public Animal()
{
Console.WriteLine("Main Animal");
}
public void Hear()
{
Console.WriteLine("All animals have ears");
}
public virtual void Talk()
{
Console.WriteLine("All animals can talk");
}
public void See()
{
Console.WriteLine("All animals can see");
}
}
The above class introduces a few new concepts. First, we create the Animal class. We've seen the constructor before. This constructor allows you to instantiate the class in your code. When the class is instantiated, we print out "Main Animal" to the console.
We then create three methods that are a part of all animals. All animals can see, hear and talk. Notice that the Talk member method has the virtual modifier. The virtual modifier allows us to override the function in the child class.
You might wonder why we chose to use the virtual modifier on the Talk method only. Part of software development is also engineering great code and understanding the way a program should function. We know that all animals speak, but we know that they speak differently. A lion roars and a monkey pounds his chest and shouts. These differences must be reflected in your code, and since we want to derive both lion and monkey from the main animal class, we need to override the Talk method's basic function. We can do this by setting the method to virtual.
Before we create a derived class, let's review how you can instantiate the Animal class and use its methods.
Animal animal = new Animal();
animal.Hear();
The above code simply prints out "All animals can hear."
We have our parent class, and now we can start inheriting from it with child classes. First, let's make a lion class and inherit from Animal.
public class Animal
{
public Animal()
{
Console.WriteLine("Main Animal");
}
public void Hear()
{
Console.WriteLine("All animals have ears");
}
public virtual void Talk()
{
Console.WriteLine("All animals can talk");
}
public void See()
{
Console.WriteLine("All animals can see");
}
}
public class Lion : Animal
{
public Lion()
{
Console.WriteLine("Lion is an animal");
}
public override void Talk()
{
Console.WriteLine("A lion roars");
}
}
We copied our Animal class, and now we have a Lion class. The "Lion : Animal" notation indicates that we want Lion to inherit from the Animal class. The format for inheritance is always "child class : parent class."
You should recognize the constructor. It's the same name as the Lion class and prints out "Lion is an animal." A new concept we haven't seen is in the Talk method. Remember that we made the Talk method in the parent class virtual. This lets us override the parent method. To override a method, you need to use the override modifier, which you can see in front of the Talk method.
Let's see what happens when we instantiate the Lion class and call each function.
Lion lion = new Lion();
lion.Hear();
lion.Talk();
lion.See();
Now let's look at the output.
Main animal
A lion is an animal
All animals can hear
A lion roars
All animals can see
You might wonder why the Animal and Lion constructor prints out their code. Because the Lion class inherits from the Animal class, the Animal class is instantiated as well. Therefore, both constructors are executed and print out their content.
We then call the Hear method. We have no Hear method in the Lion class, but the code still doesn't give you an error. It's because the Lion class inherits from the parent Animal class, so all parent class functions are available to Lion unless we purposely block them. You can block methods using the private modifier instead of the public modifier. Since we set all methods as public, they are all available to the child class.
Because we use the derived parent class method, the Hear method returns "All animals can hear."
Next, we call the Talk method. We used the virtual modifier in the parent class and the override modifier to create our own version of the Talk method. We know that a lion roars, so we want to modify the Talk method to represent the lion's characteristics. Because we used the override modifier in the Lion's Talk method, the value "A lion roars" is printed to the screen.
Finally, we call the See method. Just like the Hear method, we know that we can derive the See method without making modifications. We don't have a See method in the Lion class, so the parent class method is called instead. The output for this function call is "All animals can see."
We mentioned that you can block child classes from deriving parent class methods. Suppose we want to block a child class from changing the Hear method. We don't want the child class to call the method or have access to it. Let's take a look at the code.
public class Animal
{
public Animal()
{
Console.WriteLine("Main Animal");
}
private void Hear()
{
Console.WriteLine("All animals have ears");
}
public virtual void Talk()
{
Console.WriteLine("All animals can talk");
}
public void See()
{
Console.WriteLine("All animals can see");
}
}
public class Lion : Animal
{
public Lion()
{
Console.WriteLine("Lion is an animal");
}
public override void Talk()
{
Console.WriteLine("A lion roars");
}
}
Now, let's try to call the same methods.
Lion lion = new Lion();
lion.Hear();
lion.Talk();
lion.See();
When you call the Hear method from the parent class, the code will fail. Actually, this code will not compile, because we've attempted to use a private method in our child class code.
When you work with inheritance, you can also derive from child classes. For instance, a Monkey has several different species. You could derive species classes from the Monkey class and inherit its methods and member variables. When you use OOP, you'll have several classes deriving from other classes. You can also derive classes from multiple parent classes. For instance, suppose you want to derive your Lion class from Animal and Cat. You would create the class structure using the following code.
public class Lion : Animal, Cat
{
public Lion()
{
Console.WriteLine("Lion is an animal");
}
public override void Talk()
{
Console.WriteLine("A lion roars");
}
}
Notice the comma after the Animal class, and then the Cat class notation. Using a comma, you can inherit from multiple classes in your C# code.
You can have dozens of classes in your program, but they all come together to create one program. In this example, we made a zoo program to represent our class structures.
Polymorphism
We saw some polymorphism in the last chapter when we overloaded methods. As we mentioned in the beginning of the chapter, polymorphism let's you use the same methods for different actions. The action that's linked with the method is determined by the input you send to the method. We saw overloaded methods in the last chapter. We overloaded an Add method and passed it values that triggered the right version.
We can use our Animal and Lion classes to also represent polymorphism and overloaded methods. Let's take a look at how we can create a polymorphic Walk method that has two versions.
public class Animal
{
public Animal()
{
Console.WriteLine("Main Animal");
}
public void Hear()
{
Console.WriteLine("All animals have ears");
}
public virtual void Talk()
{
Console.WriteLine("All animals can talk");
}
public void See()
{
Console.WriteLine("All animals can see");
}
public void Walk()
{
Console.WriteLine("All animals can walk");
}
public void Walk(int legs)
{
Console.WriteLine("This animal walks with {0} legs", legs );
}
}
public class Lion : Animal
{
public Lion()
{
Console.WriteLine("Lion is an animal");
}
public override void Talk()
{
Console.WriteLine("A lion roars");
}
}
Notice that we added two Walk methods in the Animal class. Since all animals walk, it makes sense to add the method to the main Animal parent class instead of child classes. The first Walk method takes no arguments. The second Walk method takes an integer parameter that indicates the number of legs. Even though both methods have the same name, the compiler is able to distinguish the two methods from the different overloaded parameter values.
Let's take a look at how we would call this method to see polymorphism in action.
Lion lion = new Lion();
lion.Hear();
lion.Talk();
lion.See();
lion.Walk();
lion.Walk(4);
Now, let's look at the output.
Main animal
A lion is an animal
All animals can hear
A lion roars
All animals can see
All animals can walk
This animal walks with 4 legs
If you compile and run the code, it won't crash and the compiler won't give you an error even though we have two methods with the same name. The first call to the Walk method calls the function without any parameters. The second call appropriately calls the function that takes an integer parameter. This is the first way you can use polymorphism in your code. As long as the method using the same name as another has different parameters or return types, the compiler will compile and no errors are given.
The other way to use polymorphism is to override your virtual methods. We already looked at how to override your methods in the last section, but what if you want to block the child class from overriding a method without blocking it entirely? We used the private modifier in the last section to show you how to block a derived class, but the private modifier blocks all areas of your code from accessing the method.
We made the Hear method private, but we want to be able to access this method in other classes. We can use the sealed modifier to allow the child class to inherit the method but disallow it from overriding it. Let's take a look at the code.
public class Animal
{
public Animal()
{
Console.WriteLine("Main Animal");
}
public sealed void Hear()
{
Console.WriteLine("All animals have ears");
}
public virtual void Talk()
{
Console.WriteLine("All animals can talk");
}
public void See()
{
Console.WriteLine("All animals can see");
}
public void Walk()
{
Console.WriteLine("All animals can walk");
}
public void Walk(int legs)
{
Console.WriteLine("This animal walks with {0} legs", legs );
}
}
public class Lion : Animal
{
public Lion()
{
Console.WriteLine("Lion is an animal");
}
public override void Talk()
{
Console.WriteLine("A lion roars");
}
}
We only changed one item in the above code from the previous section. We changed the Hear method to sealed, and we changed the private modifier to public. Now, the Lion child class can access the method but can't change its values. Let's see what happens when we call the methods in our code.
Lion lion = new Lion();
lion.Hear();
lion.Talk();
lion.See();
lion.Walk();
lion.Walk(4);
Now, let's look at the output.
Main animal
A lion is an animal
All animals can hear
A lion roars
All animals can see
All animals can walk
This animal walks with 4 legs
Notice that we're still able to use the Hear method and the right output is displayed. Now let's try to override it.
public class Animal
{
public Animal()
{
Console.WriteLine("Main Animal");
}
public sealed void Hear()
{
Console.WriteLine("All animals have ears");
}
public virtual void Talk()
{
Console.WriteLine("All animals can talk");
}
public void See()
{
Console.WriteLine("All animals can see");
}
public void Walk()
{
Console.WriteLine("All animals can walk");
}
public void Walk(int legs)
{
Console.WriteLine("This animal walks with {0} legs", legs );
}
}
public class Lion : Animal
{
public Lion()
{
Console.WriteLine("Lion is an animal");
}
public override void Talk()
{
Console.WriteLine("A lion roars");
}
public override void Hear()
{
Console.WriteLine("A lion hears");
}
}
Try to compile the above code. The override on the Hear function throws an error, and you will be forced to fix the error before you can compile.
Polymorphism and inheritance are difficult to engineer when you're a new developer, but use these code samples to get used the development. When you decide to use classes and inheritance in your C# programs, it's best to design your classes before you start programming. This will help you avoid any errors that cause you to overhaul your class structures during development.