Polymorphism is a fundamental concept in object-oriented programming (OOP), and C# supports polymorphism through various mechanisms. Polymorphism allows objects of different types to be treated as objects of a common base type, providing flexibility and extensibility in software design.
There are two main forms of polymorphism in C#: compile-time polymorphism and runtime polymorphism.
- Compile-time polymorphism:
- Method overloading: It enables you to define multiple methods with the same name but different parameters. The appropriate method is selected at compile-time based on the arguments used during the method call.
- Operator overloading: It allows you to redefine the behavior of operators (+, -, *, etc.) for custom types.
- Runtime polymorphism:
- Inheritance and method overriding: By using inheritance, you can create a hierarchy of classes, where derived classes inherit members from their base class. Method overriding enables a derived class to provide its own implementation of a method defined in the base class, allowing objects of the derived class to be used in place of the base class objects.
- Virtual methods: You can mark a base class method as “virtual,” and derived classes can override it using the “override” keyword. The appropriate method implementation is determined dynamically at runtime based on the actual type of the object.
- Abstract classes and interfaces: An abstract class can define abstract members that must be implemented by derived classes, while interfaces provide a contract for implementing classes to adhere to. Both abstract classes and interfaces can be used to achieve polymorphism by treating objects as instances of their base abstract class or interface type.
Polymorphism allows for writing more flexible and reusable code by promoting code reuse, modularity, and extensibility. It simplifies the process of adding new functionality to existing code without modifying the existing implementation, making it a powerful technique in object-oriented programming.
C# Runtime Polymorphism Example:
Certainly! Let’s consider an example to demonstrate runtime polymorphism in C#. We’ll create a base class called Shape
and two derived classes, Circle
and Rectangle
. The Shape
class will have a virtual method called CalculateArea()
, which the derived classes will override.
using System; public class Shape { public virtual void CalculateArea() { Console.WriteLine("Calculating area of the shape..."); } } public class Circle : Shape { private double radius; public Circle(double radius) { this.radius = radius; } public override void CalculateArea() { double area = Math.PI * radius * radius; Console.WriteLine("Area of the circle: " + area); } } public class Rectangle : Shape { private double length; private double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } public override void CalculateArea() { double area = length * width; Console.WriteLine("Area of the rectangle: " + area); } } public class Program { public static void Main(string[] args) { Shape shape1 = new Circle(3.5); Shape shape2 = new Rectangle(4, 5.5); shape1.CalculateArea(); // Calls the overridden method in Circle shape2.CalculateArea(); // Calls the overridden method in Rectangle } }
In this example, we define a base class Shape
with a virtual method CalculateArea()
. The Circle
and Rectangle
classes inherit from Shape
and override the CalculateArea()
method with their own implementations.
In the Main()
method, we create two instances: shape1
of type Circle
and shape2
of type Rectangle
. Even though we’re declaring them as Shape
, we assign them objects of the derived classes.
When we call the CalculateArea()
method on shape1
and shape2
, the overridden methods in Circle
and Rectangle
are called based on the actual types of the objects. This is an example of runtime polymorphism, where the appropriate method implementation is determined at runtime based on the object’s actual type.
The output will be:
Area of the circle: 38.48451000647496 Area of the rectangle: 22
As you can see, the appropriate CalculateArea()
method implementation is called based on the runtime type of the objects, allowing us to treat different objects as instances of a common base class and invoke the appropriate behavior dynamically.
Runtime Polymorphism with Data Members:
Runtime polymorphism also applies to data members in addition to methods. Let’s modify the previous example to demonstrate runtime polymorphism with data members.
using System; public class Shape { public string Name { get; set; } public virtual void PrintInfo() { Console.WriteLine("Shape: " + Name); } } public class Circle : Shape { public double Radius { get; set; } public override void PrintInfo() { Console.WriteLine("Circle: " + Name); Console.WriteLine("Radius: " + Radius); } } public class Rectangle : Shape { public double Length { get; set; } public double Width { get; set; } public override void PrintInfo() { Console.WriteLine("Rectangle: " + Name); Console.WriteLine("Length: " + Length); Console.WriteLine("Width: " + Width); } } public class Program { public static void Main(string[] args) { Shape shape1 = new Circle { Name = "Circle", Radius = 3.5 }; Shape shape2 = new Rectangle { Name = "Rectangle", Length = 4, Width = 5.5 }; shape1.PrintInfo(); // Calls the overridden method in Circle shape2.PrintInfo(); // Calls the overridden method in Rectangle } }
In this modified example, we have added data members to the Shape
, Circle
, and Rectangle
classes. The Shape
class has a Name
property, and the derived classes have additional properties (Radius
for Circle
and Length
and Width
for Rectangle
).
We override the PrintInfo()
method in the derived classes to display the relevant information for each shape type.
In the Main()
method, we create instances of Circle
and Rectangle
but declare them as Shape
. The PrintInfo()
method is called on shape1
and shape2
, and the overridden methods in Circle
and Rectangle
are invoked based on the actual types of the objects.
The output will be:
Circle: Circle Radius: 3.5 Rectangle: Rectangle Length: 4 Width: 5.5
As you can see, the PrintInfo()
method displays the data members of the respective derived classes. This demonstrates that data members can also exhibit polymorphic behavior at runtime.