Polymorphism in C#
Polymorphism, meaning "many forms," is a fundamental concept in object-oriented programming that allows you to treat objects of different classes in a uniform way. In C#, polymorphism is primarily achieved through inheritance and interfaces, enabling methods to behave differently based on the type of object they are called on.
Types of Polymorphism in C#
C# supports two main types of polymorphism:
- Compile-time Polymorphism (Static Polymorphism): This is achieved through method overloading and operator overloading. The decision of which method or operator to call is made at compile time.
- Run-time Polymorphism (Dynamic Polymorphism): This is achieved through method overriding, where the decision of which method implementation to execute is made at run time based on the actual type of the object. This is often referred to as "late binding."
Run-time Polymorphism (Method Overriding)
Run-time polymorphism is the more commonly discussed type when referring to the "polymorphism" concept in OOP. It enables a derived class to provide a specific implementation of a method that is already provided by its base class.
Key Concepts:
- Base Class: The class from which other classes inherit.
- Derived Class: The class that inherits from a base class.
virtualkeyword: Used in the base class to indicate that a method can be overridden by a derived class.overridekeyword: Used in the derived class to provide a new implementation for avirtualmethod from the base class.abstractkeyword: Used to declare abstract classes and abstract methods. Abstract methods do not have an implementation and must be implemented by derived classes.
Example: Shape Hierarchy
Consider a base class Shape with a virtual method Draw(). Derived classes like Circle and Square can override this method to provide their specific drawing logic.
// Base class
public abstract class Shape
{
// Virtual method that can be overridden
public virtual void Draw()
{
Console.WriteLine("Drawing a generic shape.");
}
}
// Derived class 1
public class Circle : Shape
{
// Overriding the virtual method
public override void Draw()
{
Console.WriteLine("Drawing a circle.");
}
}
// Derived class 2
public class Square : Shape
{
// Overriding the virtual method
public override void Draw()
{
Console.WriteLine("Drawing a square.");
}
}
// Example usage
public class Program
{
public static void Main(string[] args)
{
Shape myCircle = new Circle();
Shape mySquare = new Square();
Shape genericShape = new Shape(); // If Shape were not abstract
myCircle.Draw(); // Output: Drawing a circle.
mySquare.Draw(); // Output: Drawing a square.
// genericShape.Draw(); // Would output: Drawing a generic shape.
}
}
In this example, even though we are calling Draw() on variables of type Shape, the actual method executed depends on the concrete type of the object (Circle or Square) at runtime.
Abstract Classes and Methods
Abstract classes are classes that cannot be instantiated directly. They are designed to be inherited from. Abstract methods are methods declared in an abstract class that do not have an implementation. Derived classes must provide an implementation for all abstract methods.
Example: Abstract Base Class
Using the Shape example, if we make it abstract and the Draw() method abstract, all derived classes will be forced to implement it.
// Abstract base class
public abstract class AbstractShape
{
// Abstract method (no implementation)
public abstract void Draw();
}
// Derived class 1
public class Triangle : AbstractShape
{
public override void Draw()
{
Console.WriteLine("Drawing a triangle.");
}
}
// Derived class 2
public class Rectangle : AbstractShape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle.");
}
}
// Example usage
public class Program
{
public static void Main(string[] args)
{
// AbstractShape shape = new AbstractShape(); // ERROR: Cannot create an instance of an abstract class.
AbstractShape myTriangle = new Triangle();
AbstractShape myRectangle = new Rectangle();
myTriangle.Draw(); // Output: Drawing a triangle.
myRectangle.Draw(); // Output: Drawing a rectangle.
}
}
This enforces a contract: any class inheriting from AbstractShape must define its own Draw() behavior.
virtual, the base class provides a default implementation. When using abstract, there is no default implementation; derived classes must provide one.
Compile-time Polymorphism (Method Overloading)
Method overloading allows you to define multiple methods in the same class with the same name but different parameter lists (different number of parameters, types of parameters, or order of parameters). The compiler determines which method to call based on the arguments provided.
Example: Overloaded Add Method
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
}
// Example usage
public class Program
{
public static void Main(string[] args)
{
Calculator calc = new Calculator();
Console.WriteLine(calc.Add(5, 10)); // Calls Add(int, int) -> Output: 15
Console.WriteLine(calc.Add(3.5, 2.1)); // Calls Add(double, double) -> Output: 5.6
Console.WriteLine(calc.Add(1, 2, 3)); // Calls Add(int, int, int) -> Output: 6
}
}
Benefits of Polymorphism
- Flexibility and Extensibility: Allows you to write code that can work with objects of different types without knowing their specific type at compile time. New types can be added without modifying existing code that uses them.
- Code Reusability: Reduces code duplication by allowing derived classes to inherit and extend functionality from base classes.
- Maintainability: Makes code easier to understand, modify, and maintain due to its organized and modular nature.
- Loose Coupling: Objects become less dependent on the concrete implementations of other objects, promoting a more flexible system design.
Understanding and effectively using polymorphism is crucial for developing robust, scalable, and maintainable object-oriented applications in C#.