Polymorphism in C#: Object-Oriented Programming

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#:

  1. Compile-Time Polymorphism: Achieved through method overloading and operator overloading.
  2. 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

FeatureAbstract ClassInterface
InstantiationCannot be instantiatedCannot be instantiated
InheritanceSingle inheritanceMultiple inheritance allowed
Member ImplementationCan include method implementationsOnly declarations (no implementations in C# < 8.0)
UsageUse when classes share a common baseUse for unrelated classes with shared behavior

Practical Use Cases of Polymorphism

  1. Strategy Design Pattern:
    • Use interfaces or base classes to define interchangeable algorithms.
  2. Plug-and-Play Architecture:
    • Allow different modules to work seamlessly without tightly coupling them.
  3. Unit Testing:
    • Use polymorphism to create mock objects that adhere to the same interface as real objects.

Best Practices for Polymorphism

  1. Program to an Interface, Not an Implementation:
    • Depend on abstractions (interfaces or base classes) rather than concrete implementations.
    Example: public void PrintShapeInfo(IShape shape) { shape.DisplayInfo(); Console.WriteLine($"Area: {shape.CalculateArea()}"); }
  2. Minimize Overriding:
    • Only override methods when necessary to prevent unexpected behavior.
  3. 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

Your email address will not be published. Required fields are marked *


Categories


Tag Cloud

.net algorithms angular api Array arrays async asynchronous basic-concepts big o blazor c# classes code components containers control-structures csharp data structures data types dictionaries docker dom dotnet framework functions git guide Inheritance javascript json leetcode linq lists loops methods MVC npm object oriented programming oop operators sorted try catch typescript web framework