Inheritance & Interfaces
C# gives you two different ways for one type to build on another. Inheritance lets a class reuse and extend another class's implementation. An interface makes no promise about implementation at all — only that a type supports a certain set of members. They solve related but distinct problems, and knowing which one you actually need is half the battle.
Inheritance: extending a base class
A derived class gets everything its base class has, plus whatever it adds on top. The : BaseClass syntax after a class name sets this up:
class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
public virtual void Speak()
{
Console.WriteLine($"{Name} makes a sound.");
}
}
class Dog : Animal
{
public Dog(string name) : base(name)
{
}
public override void Speak()
{
Console.WriteLine($"{Name} barks.");
}
}
Animal generic = new Animal("Creature");
Dog rex = new Dog("Rex");
generic.Speak();
rex.Speak();
Creature makes a sound. Rex barks.
: base(name) in Dog's constructor calls Animal's constructor first, so Name gets set the same way it would for any Animal. virtual on Animal.Speak() marks it as replaceable; override on Dog.Speak() is what actually replaces it. Without both keywords present, Dog's version wouldn't override anything — it would just be a separate method that happens to share a name.
Polymorphism: one variable, many behaviors
Because Dog is an Animal, a variable typed as Animal can legally hold a Dog — and when you call Speak() on it, C# runs the actual object's version, not the variable's declared type:
class Cat : Animal
{
public Cat(string name) : base(name)
{
}
public override void Speak()
{
Console.WriteLine($"{Name} meows.");
}
}
List<Animal> animals = new List<Animal>
{
new Dog("Rex"),
new Cat("Milo"),
new Animal("Blob")
};
foreach (Animal a in animals)
{
a.Speak();
}
Rex barks. Milo meows. Blob makes a sound.
The loop doesn't know or care which subclass each element actually is — every iteration just calls a.Speak(), and each object answers in its own way. This is polymorphism: the same call, resolved differently depending on the real type of the object at runtime.
override must override something — if the base class's method isn't marked virtual (or abstract), C# won't let you override it at all. This is intentional: a base class author has to explicitly opt a method into being replaceable.Interfaces: a promise with no implementation
An interface declares members a type must have, without saying how they work. Any class can implement any number of interfaces, regardless of what it inherits from:
interface IShape
{
double Area();
}
class Circle : IShape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public double Area()
{
return Math.PI * Radius * Radius;
}
}
class Rectangle : IShape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public double Area()
{
return Width * Height;
}
}
List<IShape> shapes = new List<IShape> { new Circle(2), new Rectangle(3, 4) };
foreach (IShape shape in shapes)
{
Console.WriteLine(shape.Area());
}
12.566370614359172 12
Circle and Rectangle share no base class in common besides object — they're related only by both implementing IShape. That's enough for a List<IShape> to hold both and call Area() on each without knowing which concrete type it's looking at. By convention, C# interface names start with a capital I.
Dog and Cat both reusing Animal's constructor logic). Reach for an interface when unrelated types just need to guarantee they support the same operations, with no shared code at all.