csharp-polimorfismo

What is and how to use polymorphism in C#

  • 4 min

Polymorphism is one of the fundamental principles of object-oriented programming (OOP), which allows objects to be treated as instances of their base class while retaining their own specific behavior.

In C#, this is achieved through:

  • Virtual methods
  • Abstract methods
  • Interfaces

If you want to learn more about object-oriented programming, here is the link to the Object-Oriented Programming Course.

Learn OOP, the most influential programming paradigm

Virtual Methods and Overriding

A virtual method is one declared in a base class that can be overridden in a derived class to modify or extend its behavior.

In the base class, the method is marked with the virtual keyword, while in the derived class override is used to specify that the method has been modified.

public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("The dog barks.");
    }
}

public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("The cat meows.");
    }
}
Copied!

In the previous example, MakeSound is a virtual method in the base class Animal.

Both Dog and Cat override this method to specify a different sound, thus allowing each subclass to define its specific behavior.

Animal myAnimal = new Dog();
myAnimal.MakeSound(); // Output: The dog barks.

myAnimal = new Cat();
myAnimal.MakeSound(); // Output: The cat meows.
Copied!

Even though the variable myAnimal is of type Animal, it executes the overridden method in the specific class (Dog or Cat).

This is polymorphism in action: it allows the MakeSound method to behave according to the concrete type of the object at runtime.

Method Hiding

In C#, there is another way to redefine the behavior of an inherited method using the new keyword. This approach is called method hiding and allows a derived class to define a new version of a base class method.

When using new instead of override, the method in the derived class hides the one in the base class. This means that when the method is called from a reference of the base class type, the original method is executed, not the one from the derived class.

public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

public class Parrot : Animal
{
    public new void MakeSound()
    {
        Console.WriteLine("The parrot imitates sounds.");
    }
}
Copied!

In this example, the Parrot class defines a new version of MakeSound using new, instead of override. This causes the method from Parrot to be present only when the object is accessed directly as type Parrot.

The new keyword is useful when you need to define a method in a subclass with the same name as the one in the base class, but it should not modify its polymorphic behavior.

Although new is less common than override, it can be a good choice if you want to provide alternative behavior without changing the structure of the base class hierarchy.

Polymorphism with Interfaces

Another way to implement polymorphism in C# is through the use of interfaces. Interfaces define contracts that classes must implement, allowing you to work with different classes that implement the same interface in a uniform way.

public interface ISound
{
    void MakeSound();
}

public class Dog : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The dog barks.");
    }
}

public class Cat : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The cat meows.");
    }
}
Copied!

In this case, the classes Dog and Cat implement the ISound interface, which means both must define the MakeSound method. This allows treating Dog and Cat polymorphically.

ISound mySound = new Dog();
mySound.MakeSound(); // Output: The dog barks.

mySound = new Cat();
mySound.MakeSound(); // Output: The cat meows.
Copied!

Using interfaces facilitates handling different types of objects in a standardized and extensible way. We can introduce new classes that implement ISound without altering the code that uses this interface, allowing for great flexibility.