CodeGym /Courses /C# SELF /Inheritance and Overriding Mistakes

Inheritance and Overriding Mistakes

C# SELF
Level 25 , Lesson 1
Available

1. Introduction

When a newbie hears the word inheritance, it might seem super simple: just grab some existing class, tweak or extend it a bit — and boom, done! But in reality, there are a bunch of gotchas. Inheritance mistakes can show up in all sorts of ways: wrong signatures, forgotten keywords, bad hierarchy design — all of this leads to nasty bugs.

Sometimes, the mistakes show up right away: your code just won't compile. Other times, the bugs only pop up at runtime, like when your shiny new SuperMegaLogger logs something totally unexpected, or worse, logs nothing at all. Sadness, frustration, hours of debugging — sound familiar?

Let's walk through the most common mistakes with inheritance and method overriding, and "heal" our code as we go.

2. Forgot virtual: Why Can't I Override This Method?

The Problem

In C#, you can only override (override) methods that are declared in the base class with the virtual keyword, or as abstract, or as override itself (in an inheritance chain). If a method doesn't have this keyword, trying to write override in the derived class will throw a compile error.


class Animal
{
    public void Speak()
    {
        Console.WriteLine("The animal says something.");
    }
}

class Cat : Animal
{
    // Compile error! The base method isn't virtual, abstract, or override.
    public override void Speak()
    {
        Console.WriteLine("Meow!");
    }
}

The error will look something like: "'Cat.Speak()': cannot override inherited member 'Animal.Speak()' because it is not marked virtual, abstract, or override".

How to Avoid This?

To override methods, always declare those methods in the base class as virtual. By the way, if you're designing classes for extension, always think about which methods might be useful to override.


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

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

Why bother?
Declaring a method as virtual means you're explicitly allowing subclasses to change its behavior, and the compiler becomes your buddy — it won't let you accidentally override the "wrong" method.

3. Messing Up override and new

Sometimes the opposite happens: the author of a derived class wants to "override" a method, but the base class method isn't virtual. Here, the compiler won't let you use override, but it will let you use new. But that's a totally different thing!


class Dog : Animal
{
    // This isn't override, it's hiding the base class method.
    public new void Speak()
    {
        Console.WriteLine("Woof!");
    }
}

If you call this method through a Dog reference — all good:


Dog dog = new Dog();
dog.Speak(); // "Woof!"

But if you use a base type reference, the base method gets called:


Animal dog2 = new Dog();
dog2.Speak(); // "The animal makes a sound."

Explanation:
The new keyword does NOT override the method, it hides the parent one. This is called "hiding". This can be confusing when polymorphism doesn't work the way you expect.

How to Avoid new vs override Traps?

If you want classic overriding with polymorphism — use virtual/override. If you're adding totally new functionality (or you really want to hide the base method, but be careful!), then use new.

Situation Keyword Polymorphism works? Behavior when accessed via base type
Change behavior override Yes Derived class method is called
Hide/replace method new No Base class method is called

4. Method Signature Mismatch

Newbies (and sometimes even experienced devs) mess up by changing parameter types or return types in the derived method. For example, if the base method is public virtual void Print(string message), but in the derived class you "override" public override void Print(object message), that's NOT an override, it's a new method definition.


class Printer
{
    public virtual void Print(string msg)
    {
        Console.WriteLine("Base printer: " + msg);
    }
}

class SmartPrinter : Printer
{
    // Compile error! Signature doesn't match the base method.
    public override void Print(object msg)
    {
        Console.WriteLine("Smart printer: " + msg);
    }
}

Tip:
The method name, return type, and parameters (types, count, and order) all have to match.

If you accidentally change a parameter type or typo the name — the compiler will warn you.

5. Access Modifier Mismatch

Another classic newbie trap — access modifiers. The derived method can't be more restrictive than the base one. Here's some bad code:


public class Vehicle
{
    public virtual void StartEngine() { /* ... */ }
}

public class Car : Vehicle
{
    // Error! 'private' is more restrictive than 'public' in the base method.
    private override void StartEngine() { /* ... */ }
}

What to do?
The access modifier in the derived method should be the same or more open than in the base method. Most of the time, that's public or protected.

6. Missing/Extra Abstract Method

If a method in the base class is declared as abstract, then the derived class MUST override it, otherwise the class also becomes abstract (and can't be instantiated).


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

class Circle : Shape
{
    // Error! Abstract method Area() not implemented
}

Solution:
You need to implement this method:


class Circle : Shape
{
    public override double Area()
    {
        return 3.14 * 2 * 2; // Roughly...
    }
}

7. Calling the Base Implementation: base.Method()

Sometimes you don't want to completely replace a method's implementation, but just add something to it. In this case, people often forget (or don't know) that you can call the base class implementation in the derived method using the base keyword.


class Logger
{
    public virtual void Log(string msg)
    {
        Console.WriteLine("Base log: " + msg);
    }
}

class FancyLogger : Logger
{
    public override void Log(string msg)
    {
        // You can add some "fancy stuff" and call the base method:
        Console.WriteLine("[FANCY] " + msg);
        base.Log(msg);
    }
}

Explanation:
If you don't call base.Log(msg), the logic in the base class will be totally lost.

8. Calling the Base Constructor (base)

If the base class requires parameters in its constructor, the derived class must explicitly call the appropriate constructor using the base keyword.


class Engine
{
    public Engine(int cylinders)
    {
        Console.WriteLine("Engine with cylinders: " + cylinders);
    }
}

class RaceEngine : Engine
{
    // Compile error! Engine doesn't have a default constructor.
    public RaceEngine() { }
}

// Fixed version:
class RaceEngine2 : Engine
{
    public RaceEngine2() : base(8) // Explicitly call the base constructor
    {
        Console.WriteLine("Race engine ready!");
    }
}

9. Forgot to Mark Methods as sealed

Sometimes you want to prevent further overriding of a method in derived classes — that's what the sealed keyword is for, used together with override. If you don't use it, someone might override your method and break your expectations or logic.


class Hero
{
    public virtual void Attack() => Console.WriteLine("Hero attacks!");
}
class Warrior : Hero
{
    public sealed override void Attack() => Console.WriteLine("Warrior strikes!");
}
class Mutant : Warrior
{
    // Error! Attack method is sealed above.
    // public override void Attack() { ... }
}

Explanation:
This way, you "seal" the implementation at this level and subclasses can't change it anymore.

10. Extra or Wrong Overloads Instead of Overriding

Sometimes devs confuse overloading (overloading) and overriding (overriding). Overloading means defining a method with the same name but a different signature (like a different number of parameters), and it has nothing to do with polymorphism.


class Animal
{
    public virtual void Eat()
    {
        Console.WriteLine("The animal eats.");
    }
}

class Panda : Animal
{
    // This is NOT override! Just a new overloaded method.
    public void Eat(string what)
    {
        Console.WriteLine("Panda eats: " + what);
    }
}

...

Animal a = new Panda();
a.Eat(); // If the method is overridden — the derived class implementation is called. If not — base is used
// a.Eat("bamboo"); // Compile error: Animal doesn't have such a method

To add polymorphic behavior, you need to override, not just overload.

11. Formal and Informal Design Mistakes

When classes are designed poorly, inheritance chains get messy:

  • internal "carousel" of overrides without proper use of base.,
  • inconsistent use of modifiers,
  • hierarchies that are too deep or unclear,
  • methods where part of the logic is "smeared" across inheritance levels,
  • no comments for virtual/abstract methods,
  • non-obvious side effects (like calling a virtual method in the base class constructor when the derived class isn't fully initialized yet).

Tip:
If you feel lost — don't be lazy, draw the hierarchy on paper (old school but it works!) and label which method is where, who overrides what, what calls what, and where base. should be called.
In real projects, it's also super important to document what you expect to be overridden in each method.

2
Task
C# SELF, level 25, lesson 1
Locked
Missing `virtual` Error When Inheriting a Method
Missing `virtual` Error When Inheriting a Method
2
Task
C# SELF, level 25, lesson 1
Locked
Method hiding error using `new`
Method hiding error using `new`
Comments (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Marian Level 65, Poznan, Poland
9 February 2026
The task is simplistic, but the requirements are so unclear/convoluted that can't validate it succesfully. Just drop it and go ahead :(