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.

9 responses to “Polymorphism in C#: Object-Oriented Programming”

  1. class Avatar
    class

    SRT9nXUXVPV

  2. MyName Avatar
    MyName

    POmzopep YtucDC vqtyB lpiwa hmf mfmNX

  3. keonhacai net bet88 Avatar
    keonhacai net bet88

    One challenge I keep running into with microservices is managing distributed transactions. We’ve been using the Saga pattern with MassTransit, but I’m curious what others are using for workflow orchestration in .NET ecosystems.

  4. world of skeletons hidden stars Avatar
    world of skeletons hidden stars

    Solid advice! I’d also recommend looking at the FluentValidation library for input validation in ASP.NET Core – it integrates seamlessly and keeps validation logic clean and testable.

  5. spot unique animal Avatar
    spot unique animal

    Really helpful article! I’m curious how this approach scales when you have multiple instances behind a load balancer. Have you considered using a distributed cache like Redis for state management?

  6. christmas spot the difference Avatar
    christmas spot the difference

    Insightful read. We recently containerized a legacy .NET Framework app using Windows containers and the migration was smoother than expected. Docker Compose made the local dev experience much better.

  7. s666welcome Avatar
    s666welcome

    Great article! For monitoring we use Prometheus with the dotnet-counter library to expose custom metrics. Grafana dashboards then give us real-time visibility into application performance.

  8. Griffin1316 Avatar
    Griffin1316

    I found this really useful. When deploying to AWS ECS, we use task definition revisions with CloudFormation to manage infrastructure as code. Rolling updates with a minimum healthy percent of 100% eliminated downtime.

  9. Gianna3041 Avatar
    Gianna3041

    I found this really useful. When deploying to AWS ECS, we use task definition revisions with CloudFormation to manage infrastructure as code. Rolling updates with a minimum healthy percent of 100% eliminated downtime.

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 javascript json leetcode linq lists loops methods npm object oriented programming oop operators promisses Python sorted try catch tutorial typescript web framework