Polymorphism is one of the fundamental concepts in object-oriented programming (OOP).
In C#, polymorphism allows objects to be treated as instances of their base class rather than their derived class.
This enables you to call derived class methods through a base class reference, enhancing code flexibility and extensibility.
This tutorial will cover:
1. Overview of Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common base class. There are two main types of polymorphism in C#:
- Compile-time (Static) Polymorphism: Achieved through method overloading or operator overloading.
- Run-time (Dynamic) Polymorphism: Achieved through method overriding, where a derived class overrides a base class method. Run-time polymorphism is what we’ll focus on in this tutorial.
2. Method Overriding with virtual and override
In C#, method overriding is the foundation of run-time polymorphism.
It allows a derived class to provide a specific implementation of a method already defined in its base class.
You do this by marking the method in the base class with the virtual keyword and the method in the derived class with the override keyword.
Example: Method Overriding
using System; public class Animal { public virtual void Speak() { Console.WriteLine("The animal makes a sound."); } } public class Dog : Animal { public override void Speak() { Console.WriteLine("The dog barks."); } } public class Cat : Animal { public override void Speak() { Console.WriteLine("The cat meows."); } } public class PolymorphismExample { public static void Main() { Animal myAnimal = new Animal(); myAnimal.Speak(); // Output: The animal makes a sound. Animal myDog = new Dog(); myDog.Speak(); // Output: The dog barks. Animal myCat = new Cat(); myCat.Speak(); // Output: The cat meows. } }
Explanation:
- The Speak method in Animal is marked as virtual, allowing it to be overridden in Dog and Cat.
- Using override, Dog and Cat provide their specific implementations of Speak.
- Polymorphism enables us to call the correct Speak method based on the object type, even when using a base class reference (Animal).
3. Using Polymorphism with Abstract Classes
An abstract class provides a base for other classes to inherit from, but it cannot be instantiated itself.
In C#, abstract methods in an abstract class must be overridden in any non-abstract derived class, providing a powerful way to enforce polymorphic behavior.
Example: Abstract Class and Abstract Method
using System; public abstract class Shape { public abstract void Draw(); // Abstract method with no implementation } public class Circle : Shape { public override void Draw() { Console.WriteLine("Drawing a circle."); } } public class Rectangle : Shape { public override void Draw() { Console.WriteLine("Drawing a rectangle."); } } public class AbstractClassExample { public static void Main() { Shape circle = new Circle(); Shape rectangle = new Rectangle(); circle.Draw(); // Output: Drawing a circle. rectangle.Draw(); // Output: Drawing a rectangle. } }
Explanation:
- Shape is an abstract class with an abstract method Draw.
- Circle and Rectangle inherit from Shape and provide their implementations of Draw.
- Polymorphism allows us to call Draw on Shape references, and the correct implementation is invoked based on the object’s actual type.
4. Interface-Based Polymorphism
Interfaces define a contract that implementing classes must follow. In C#, interfaces enable polymorphism by allowing different classes to be treated as the same type if they implement the same interface. This is useful when unrelated classes share common behavior.
Example: Interface-Based Polymorphism
using System; public interface IMovable { void Move(); } public class Car : IMovable { public void Move() { Console.WriteLine("The car drives forward."); } } public class Person : IMovable { public void Move() { Console.WriteLine("The person walks forward."); } } public class InterfaceExample { public static void Main() { IMovable car = new Car(); IMovable person = new Person(); car.Move(); // Output: The car drives forward. person.Move(); // Output: The person walks forward. } }
Explanation:
- The IMovable interface defines a Move method.
- Car and Person both implement the IMovable interface and provide their implementations of Move.
- Through polymorphism, we call Move on an IMovable reference, invoking the appropriate implementation based on the actual object type (Car or Person).
5. Practical Examples of Polymorphism
Example 1: Payment Processing System
Imagine a payment processing system that handles different types of payments, such as CreditCardPayment and PaypalPayment. Using polymorphism, we can treat all payment methods as Payment types.
using System; public abstract class Payment { public abstract void ProcessPayment(); } public class CreditCardPayment : Payment { public override void ProcessPayment() { Console.WriteLine("Processing credit card payment."); } } public class PaypalPayment : Payment { public override void ProcessPayment() { Console.WriteLine("Processing PayPal payment."); } } public class PaymentExample { public static void Main() { Payment creditCard = new CreditCardPayment(); Payment paypal = new PaypalPayment(); creditCard.ProcessPayment(); // Output: Processing credit card payment. paypal.ProcessPayment(); // Output: Processing PayPal payment. } }
Explanation:
- Payment is an abstract class with the ProcessPayment method.
- CreditCardPayment and PaypalPayment provide specific implementations of ProcessPayment.
- We treat both payment methods as Payment types, enabling polymorphism to call the correct method at runtime.
Example 2: Employee Payroll System
In an employee payroll system, different types of employees (e.g., FullTimeEmployee and ContractEmployee) can have different payment calculation methods. Using polymorphism, we can simplify payroll processing.
using System; public abstract class Employee { public string Name { get; set; } public abstract void CalculatePay(); } public class FullTimeEmployee : Employee { public override void CalculatePay() { Console.WriteLine(Name + " receives a monthly salary."); } } public class ContractEmployee : Employee { public override void CalculatePay() { Console.WriteLine(Name + " is paid based on contract hours."); } } public class PayrollExample { public static void Main() { Employee fullTime = new FullTimeEmployee { Name = "Alice" }; Employee contract = new ContractEmployee { Name = "Bob" }; fullTime.CalculatePay(); // Output: Alice receives a monthly salary. contract.CalculatePay(); // Output: Bob is paid based on contract hours. } }
Explanation:
- Employee is an abstract class with the CalculatePay method.
- FullTimeEmployee and ContractEmployee provide specific implementations.
- The CalculatePay method is called based on the employee type, demonstrating polymorphic behavior.
Example 3: Zoo Simulation
In a zoo simulation, each animal could have different behaviors, but all share the same base class or interface, allowing for polymorphic handling of animal actions.
using System; public abstract class Animal { public abstract void MakeSound(); } public class Lion : Animal { public override void MakeSound() { Console.WriteLine("The lion roars."); } } public class Elephant : Animal { public override void MakeSound() { Console.WriteLine("The elephant trumpets."); } } public class ZooExample { public static void Main() { Animal lion = new Lion(); Animal elephant = new Elephant(); lion.MakeSound(); // Output: The lion roars. elephant.MakeSound(); // Output: The elephant trumpets. } }
Explanation:
- Animal is an abstract class with the MakeSound method.
- Lion and Elephant provide their specific implementations.
- The MakeSound method behaves polymorphically, invoking the correct sound based on the animal type.
Summary
Polymorphism in C# is essential for designing flexible and extensible code.
Here’s a recap of the key concepts:
- Method Overriding: Achieved with virtual in the base class and override in the derived class.
- Abstract Classes: Provide a base class with abstract methods that must be implemented in derived classes.
- Interfaces: Enable polymorphism by defining a contract that classes implement.
- Practical Use Cases: Polymorphism is useful in applications like payment processing, employee payroll, and simulations, where objects share a base class but have unique behaviors.
Polymorphism helps create scalable, organized, and maintainable code by allowing a single interface to represent different implementations, enabling objects to behave differently while being treated as the same type.