Polymorphism is a fundamental concept in object-oriented programming (OOP) that allows objects to take on many forms. In C#, it provides the ability to define methods in a base class and override or implement them in derived classes. This makes your code more flexible, reusable, and easier to maintain.
In this post, we’ll explore the essence of polymorphism, dive into method overriding, interfaces, and abstract classes, and learn how to use them effectively in C#.
What is Polymorphism?
The term polymorphism comes from the Greek words poly (many) and morph (forms). In programming, it means that a single interface or method can represent different types or behaviors. Polymorphism is implemented in two primary ways in C#:
- Compile-Time Polymorphism: Achieved through method overloading and operator overloading.
- Runtime Polymorphism: Achieved through method overriding using base and derived classes, abstract classes, and interfaces.
This post focuses on runtime polymorphism.
Method Overriding in C#
Method overriding allows a derived class to provide a specific implementation of a method defined in a base class. This is done using the virtual
keyword in the base class and the override
keyword in the derived class.
Example:
// Base class
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal makes a sound.");
}
}
// Derived class
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Dog barks.");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Cat meows.");
}
}
// Using polymorphism
Animal myAnimal;
myAnimal = new Dog();
myAnimal.Speak(); // Output: Dog barks.
myAnimal = new Cat();
myAnimal.Speak(); // Output: Cat meows.
Here, the Speak
method behaves differently depending on the type of object.
Abstract Classes
Abstract classes serve as a blueprint for derived classes. You cannot instantiate an abstract class, but you can define abstract methods that must be implemented in derived classes.
Syntax:
public abstract class Shape
{
public abstract double CalculateArea();
public void DisplayInfo()
{
Console.WriteLine("This is a shape.");
}
}
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
public class Rectangle : Shape
{
public double Length { get; set; }
public double Width { get; set; }
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}
public override double CalculateArea()
{
return Length * Width;
}
}
// Using abstract classes
Shape myShape = new Circle(5);
Console.WriteLine(myShape.CalculateArea()); // Output: 78.54
myShape = new Rectangle(4, 6);
Console.WriteLine(myShape.CalculateArea()); // Output: 24
In this example, the CalculateArea
method is defined in the Shape
class but implemented differently in Circle
and Rectangle
.
Interfaces
An interface defines a contract that classes must adhere to. Unlike abstract classes, interfaces do not provide any implementation; they only declare members that implementing classes must define.
Syntax:
public interface IShape
{
double CalculateArea();
void DisplayInfo();
}
public class Circle : IShape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public double CalculateArea()
{
return Math.PI * Radius * Radius;
}
public void DisplayInfo()
{
Console.WriteLine("This is a circle.");
}
}
public class Rectangle : IShape
{
public double Length { get; set; }
public double Width { get; set; }
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}
public double CalculateArea()
{
return Length * Width;
}
public void DisplayInfo()
{
Console.WriteLine("This is a rectangle.");
}
}
// Using interfaces
IShape shape = new Circle(5);
shape.DisplayInfo(); // Output: This is a circle.
Console.WriteLine(shape.CalculateArea()); // Output: 78.54
shape = new Rectangle(4, 6);
shape.DisplayInfo(); // Output: This is a rectangle.
Console.WriteLine(shape.CalculateArea()); // Output: 24
Interfaces are ideal for scenarios where multiple classes share common functionality but do not share a common base class.
Abstract Classes vs. Interfaces
Feature | Abstract Class | Interface |
---|---|---|
Instantiation | Cannot be instantiated | Cannot be instantiated |
Inheritance | Single inheritance | Multiple inheritance allowed |
Member Implementation | Can include method implementations | Only declarations (no implementations in C# < 8.0) |
Usage | Use when classes share a common base | Use for unrelated classes with shared behavior |
Practical Use Cases of Polymorphism
- Strategy Design Pattern:
- Use interfaces or base classes to define interchangeable algorithms.
- Plug-and-Play Architecture:
- Allow different modules to work seamlessly without tightly coupling them.
- Unit Testing:
- Use polymorphism to create mock objects that adhere to the same interface as real objects.
Best Practices for Polymorphism
- Program to an Interface, Not an Implementation:
- Depend on abstractions (interfaces or base classes) rather than concrete implementations.
public void PrintShapeInfo(IShape shape) { shape.DisplayInfo(); Console.WriteLine($"Area: {shape.CalculateArea()}"); }
- Minimize Overriding:
- Only override methods when necessary to prevent unexpected behavior.
- Combine with Dependency Injection:
- Use interfaces and polymorphism to inject dependencies into your classes for flexibility and testability.
Conclusion
Polymorphism is a powerful feature that enhances the flexibility and scalability of your applications. By understanding method overriding, interfaces, and abstract classes, you can design clean, modular, and extensible code. Practice applying polymorphism in real-world scenarios to harness its full potential in your development projects.
Leave a Reply