404 Not Found

404 Not Found


nginx

Polymorphism

Polymorphism Overview

Polymorphism is one of the core features of object-oriented programming, referring to the ability of a single interface to exhibit different behaviors on different objects. In C#, polymorphism comes in two forms:

This lesson focuses on runtime polymorphism, which is the most critical capability in object-oriented design.

virtual and override

The base class marks a method as overridable with virtual, and the derived class provides a new implementation with override. At runtime, which method is called depends on the object's actual type (not the declared type of the variable) — this is dynamic dispatch.

Example

CSHARP
using System;

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

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

class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Cat meows");
    }
}

class Program
{
    static void Main()
    {
        Animal a = new Dog();
        a.Speak();

        Animal b = new Cat();
        b.Speak();
    }
}
▶ Try it Yourself
TEXT
Dog barks
Cat meows

The variables are declared as Animal, but the actual objects are Dog and Cat. At runtime, each overridden version is called — this is the core effect of polymorphism.

Practical Application: Unified Interface, Different Implementations

The most common scenario for polymorphism is using a base class reference array or collection to manage different derived class objects, calling a unified interface while each object responds in its own way.

Example

CSHARP
using System;

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

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

class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Cat meows");
    }
}

class Cow : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Cow moos");
    }
}

class Program
{
    static void MakeThemSpeak(Animal[] animals)
    {
        foreach (Animal a in animals)
        {
            a.Speak();
        }
    }

    static void Main()
    {
        Animal[] animals = { new Dog(), new Cat(), new Cow() };
        MakeThemSpeak(animals);
    }
}
▶ Try it Yourself
TEXT
Dog barks
Cat meows
Cow moos

The MakeThemSpeak method depends only on the Animal type. It does not need to know which specific animal it is dealing with, and adding new derived classes requires no modification to this method.

override vs. new

Using the new keyword in a derived class can hide a base class method, but this does not participate in polymorphism. override participates in dynamic dispatch, while new hiding selects the method at compile time based on the declared type.

Example

CSHARP
using System;

class Base
{
    public virtual void Print()
    {
        Console.WriteLine("Base.Print");
    }
}

class OverrideDerived : Base
{
    public override void Print()
    {
        Console.WriteLine("OverrideDerived.Print");
    }
}

class NewDerived : Base
{
    public new void Print()
    {
        Console.WriteLine("NewDerived.Print");
    }
}

class Program
{
    static void Main()
    {
        Base obj1 = new OverrideDerived();
        obj1.Print();

        Base obj2 = new NewDerived();
        obj2.Print();

        NewDerived obj3 = new NewDerived();
        obj3.Print();
    }
}
▶ Try it Yourself
TEXT
OverrideDerived.Print
Base.Print
NewDerived.Print
💡 Always use override when you need polymorphic behavior. Use new only for method hiding in special cases.

abstract Methods and Abstract Classes

An abstract method has no implementation and forces derived classes to override it. A class containing abstract methods must itself be declared abstract and cannot be instantiated directly.

Example

CSHARP
using System;

abstract class Shape
{
    public abstract double Area();
}

class Circle : Shape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double Area()
    {
        return Math.PI * Radius * Radius;
    }
}

class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double Area()
    {
        return Width * Height;
    }
}

class Program
{
    static void PrintArea(Shape shape)
    {
        Console.WriteLine($"Area: {shape.Area():F2}");
    }

    static void Main()
    {
        Shape[] shapes = { new Circle(3), new Rectangle(4, 5) };
        foreach (Shape s in shapes)
        {
            PrintArea(s);
        }
    }
}
▶ Try it Yourself
TEXT
Area: 28.27
Area: 20.00

The abstract class defines "what it is", while the derived classes decide "how it works". All shapes compute their area through the unified Area() interface.

Method Chaining: Multi-Level Overriding

virtual/override can be overridden layer by layer along the inheritance chain. A derived class can call the parent implementation via base. and extend it further.

Example

CSHARP
using System;

class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal speaks");
    }
}

class Dog : Animal
{
    public override void Speak()
    {
        base.Speak();
        Console.WriteLine("Dog barks");
    }
}

class Puppy : Dog
{
    public override void Speak()
    {
        base.Speak();
        Console.WriteLine("Puppy yips");
    }
}

class Program
{
    static void Main()
    {
        Animal a = new Puppy();
        a.Speak();
    }
}
▶ Try it Yourself
TEXT
Animal speaks
Dog barks
Puppy yips

Puppy.Speak() calls base.Speak(), which triggers Dog.Speak(), which in turn calls base.Speak() triggering Animal.Speak(), forming a chain of output from base class to derived class.

Overriding ToString

All types in C# inherit from object, and its ToString() method is virtual. Overriding it allows an object to display meaningful text when output.

Example

CSHARP
using System;

class Student
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Student(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public override string ToString()
    {
        return $"{Name} (Age: {Age})";
    }
}

class Program
{
    static void Main()
    {
        Student s = new Student("Alice", 20);
        Console.WriteLine(s);
        Console.WriteLine(s.ToString());
    }
}
▶ Try it Yourself
TEXT
Alice (Age: 20)
Alice (Age: 20)

Console.WriteLine automatically calls ToString() internally. After overriding, the output changes from the default type name to a custom format.

The Liskov Substitution Principle

The Liskov Substitution Principle (LSP) is a fundamental principle of object-oriented design: derived class objects must be able to replace their base class objects without altering the correctness of the program.

⚠️ Typical LSP violation: a derived class overrides a method and throws an exception not declared in the base class, or returns a result that violates the base class contract.

Example

CSHARP
using System;

class Bird
{
    public virtual void Fly()
    {
        Console.WriteLine("Bird flies");
    }
}

class Swallow : Bird
{
    public override void Fly()
    {
        Console.WriteLine("Swallow flies high");
    }
}

class Penguin : Bird
{
    public override void Fly()
    {
        throw new NotSupportedException("Penguin cannot fly");
    }
}

class Program
{
    static void LetFly(Bird bird)
    {
        bird.Fly();
    }

    static void Main()
    {
        LetFly(new Swallow());
        LetFly(new Penguin());
    }
}
▶ Try it Yourself
TEXT
Swallow flies high

Unhandled Exception: System.NotSupportedException: Penguin cannot fly

Penguin inherits from Bird but overrides Fly to throw an exception, breaking the base class's "birds can fly" contract and violating LSP. The correct approach is to redesign the inheritance hierarchy, such as extracting "flying ability" into a separate interface.

❓ FAQ

Q Can a virtual method be left un-overridden?
A Yes. If not overridden, the base class default implementation is used.
Q Can an override method be overridden again?
A Yes. An override method implicitly carries virtual semantics, so further derived classes can continue to override it.
Q Which is better, new or override?
A Use override when you need polymorphic behavior. Use new only when you must hide a method and are certain polymorphism is not needed.
Q What is the difference between an abstract class and an interface?
A An abstract class can have fields and implementation code; an interface only defines a contract. A class can inherit from only one abstract class but can implement multiple interfaces.
Q What happens if I don't override ToString()?
A The default returns the fully qualified type name; for example, Student would output Student.

📖 Summary

📝 Exercises

  1. Define a Vehicle base class with a virtual void Move() method, derive Car and Bicycle classes that override Move(), and call them uniformly through a Vehicle[] array in Main
  2. Define an abstract class Payment with an abstract void Pay(decimal amount) method, derive CashPayment and CardPayment classes that implement their own payment logic
  3. Create a Book class and override ToString() to output the book title and price; verify the effect of Console.WriteLine(book)
  4. Write code demonstrating the difference between override and new: use a base class reference to call two derived class objects respectively, and observe the output difference
  5. Consider: if Square inherits from Rectangle but overrides SetWidth/SetHeight so that the side lengths are always equal, does this violate the Liskov Substitution Principle? Explain your reasoning
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏