1. Introduction
Today's topic might seem a bit abstract, but trust me, you can't go far without it. We're gonna talk about inheritance in programming. Today we'll just touch on it a bit, and later we'll dig in deeper.
Imagine you're writing a program to keep track of animals in a zoo. You've got different kinds of animals: lions, tigers, elephants, parrots. They're all animals. But each has its own quirks:
- Lion has a mane.
- Tiger is striped.
- Elephant is huge and has a trunk.
- Parrot can talk.
If we described each animal separately, the code would look super similar:
// Lion
public class Lion
{
public string Name { get; set; }
public int Age { get; set; }
public string Species { get; set; } = "Lion"; // Always "Lion"
public void Eat() { Console.WriteLine("Lion eats meat."); }
public void Sleep() { Console.WriteLine("Lion sleeps."); }
public void Roar() { Console.WriteLine("Lion roars!"); }
public string ManeColor { get; set; } // Lion's special feature - mane
}
// Tiger
public class Tiger
{
public string Name { get; set; }
public int Age { get; set; }
public string Species { get; set; } = "Tiger"; // Always "Tiger"
public void Eat() { Console.WriteLine("Tiger eats meat."); }
public void Sleep() { Console.WriteLine("Tiger sleeps."); }
public void Stride() { Console.WriteLine("Tiger sneaks."); }
public string StripePattern { get; set; } // Tiger's special feature - stripes
}
See how much duplicate code there is? Name, Age, Eat(), Sleep() — that's all common for all animals! What if we have 100 animal types? Yikes!
That's where inheritance comes to the rescue.
2. What is class inheritance in C#?
Inheritance is one of the fundamental principles of object-oriented programming (OOP). It lets you create new classes based on existing ones. The new class (the child, derived class) gets (inherits) all the properties (fields) and behavior (methods) of its parent class (base class).
It's like in real life: you inherit some traits from your parents, but you also have your own unique features.
In C#, inheritance is shown with a colon : after the child class name:
// Parent (base) class - Animal
public class Animal
{
public string Name { get; set; } // Animal's name
public int Age { get; set; } // Animal's age
public void Eat()
{
Console.WriteLine($"{Name} eats.");
}
public void Sleep()
{
Console.WriteLine($"{Name} sleeps.");
}
}
// Child (derived) class - Lion inherits from Animal
public class Lion : Animal // <-- This is inheritance!
{
public string ManeColor { get; set; } // Lion's unique property
public void Roar() // Lion's unique behavior
{
Console.WriteLine($"{Name} roars: RRRRRR!");
}
}
// Another child class - Tiger inherits from Animal
public class Tiger : Animal
{
public string StripePattern { get; set; } // Tiger's unique property
public void Stride() // Tiger's unique behavior
{
Console.WriteLine($"{Name} sneaks silently.");
}
}
Now, if we create a Lion or Tiger object, they'll automatically have the Name and Age properties, as well as the Eat() and Sleep() methods, because they inherited them from Animal.
class Program
{
static void Main(string[] args)
{
Lion simba = new Lion();
simba.Name = "Simba";
simba.Age = 5;
simba.ManeColor = "Golden";
simba.Eat(); // Method inherited from Animal
simba.Sleep(); // Method inherited from Animal
simba.Roar(); // Lion's own method
Console.WriteLine($"{simba.Name} - age: {simba.Age}, mane color: {simba.ManeColor}");
Tiger shereKhan = new Tiger();
shereKhan.Name = "Shere Khan";
shereKhan.Age = 7;
shereKhan.StripePattern = "Classic";
shereKhan.Eat(); // Method inherited from Animal
shereKhan.Sleep(); // Method inherited from Animal
shereKhan.Stride(); // Tiger's own method
Console.WriteLine($"{shereKhan.Name} - age: {shereKhan.Age}, pattern: {shereKhan.StripePattern}");
}
}
Key ideas of inheritance:
- Code Reusability: You avoid duplicating code by putting shared logic in the base class.
- Hierarchy: You get a logical "general-specific" structure (Animal → Lion/Tiger).
- Polymorphism: (We'll just mention it for now, more on this later) You can work with child class objects using references to their base class. For example, you can have a list of Animal and put both lions and tigers in there, and then call Eat() on them.
3. Calling base class constructors (base)
When you create a child class object (like new Lion()), C# automatically calls the base class constructor (in our case Animal) before the child class constructor. This makes sure the base part of the object is set up right.
Sometimes you need to pass parameters to the base class constructor. For that, you use the base keyword after the child class constructor signature.
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
// Base class constructor
public Animal(string name, int age)
{
Name = name;
Age = age;
Console.WriteLine($"Animal constructor: Created animal {Name}, age {Age}.");
}
public void Eat()
{
Console.WriteLine($"{Name} eats.");
}
public void Sleep()
{
Console.WriteLine($"{Name} sleeps.");
}
}
public class Lion : Animal
{
public string ManeColor { get; set; }
// Lion constructor that calls the base class Animal constructor
public Lion(string name, int age, string maneColor) : base(name, age) // <-- Here's 'base'!
{
ManeColor = maneColor;
Console.WriteLine($"Lion constructor: Mane color {ManeColor}.");
}
public void Roar()
{
Console.WriteLine($"{Name} roars: RRRRRR!");
}
}
class Program
{
static void Main(string[] args)
{
// When creating Lion, first Animal constructor is called, then Lion
Lion simba = new Lion("Simba", 5, "Golden");
simba.Roar();
}
}
What happens when you run new Lion("Simba", 5, "Golden"):
- The Lion(string name, int age, string maneColor) constructor is called.
- Thanks to : base(name, age), control goes to the Animal(string name, int age) constructor.
- The Animal constructor code runs, and you'll see Animal constructor: Created animal Simba, age 5.
- Then control goes back to the Lion constructor.
- The Lion constructor code runs, and you'll see Lion constructor: Mane color Golden.
This is super important: the base class always gets initialized first!
4. The is and as operators
In the world of C# programming, you'll often run into situations where you need to check an object's type or try to cast it to another type. That's where the is and as operators come in. They're especially handy when working with inheritance and polymorphism (more on that later), but you can get the basics now.
The is operator (type check)
The is operator lets you check if an object is compatible with a certain type. It returns true if the object can be cast to the given type, and false otherwise.
Syntax: expression is Type
static void AnalyzeObject(object obj)
{
if (obj is string)
{
Console.WriteLine("This is a string!");
}
else if (obj is int)
{
Console.WriteLine("This is an integer!");
}
else
{
Console.WriteLine("This is something else.");
}
}
static void Main()
{
AnalyzeObject("Hello, world!"); // This is a string!
AnalyzeObject(123); // This is an integer!
AnalyzeObject(3.14); // This is something else.
AnalyzeObject(new int[] { 1, 2 }); // This is something else.
}
is with pattern matching:
Modern C# lets you use is not just to check the type, but also to immediately create a variable of that type if it matches. This is called "pattern matching" and makes your code way cleaner.
static void AnalyzeObjectWithPatternMatching(object obj)
{
if (obj is string message) // If obj is a string, assign it to message
{
Console.WriteLine($"This is a string, its length: {message.Length}");
}
else if (obj is int number) // If obj is int, assign it to number
{
Console.WriteLine($"This is a number, times 2: {number * 2}");
}
else
{
Console.WriteLine("Type not recognized.");
}
}
static void Main()
{
AnalyzeObjectWithPatternMatching("Example"); // This is a string, its length: 7
AnalyzeObjectWithPatternMatching(50); // This is a number, times 2: 100
AnalyzeObjectWithPatternMatching(new object()); // Type not recognized.
}
This is + pattern matching approach is way better than the old-school type casting and then checking for null, since it's safer and more readable.
The as operator (safe type casting)
The as operator tries to cast an object to the specified type. If it works, you get the object as that type. If it doesn't (the object isn't compatible with the target type), as returns null instead of throwing an exception. That's why as is a "safe" cast.
Syntax: expression as Type
static void ProcessData(object data)
{
string text = data as string; // Try to cast data to string
if (text != null) // Check if the cast worked
{
Console.WriteLine($"Processing string: {text.ToUpper()}");
}
else
{
Console.WriteLine("Data is not a string.");
}
}
static void Main()
{
ProcessData("hello"); // Processing string: HELLO
ProcessData(123); // Data is not a string.
ProcessData(null); // Data is not a string.
}
When to use as instead of direct casting (Type)expression:
- Safety: If you're not sure the object is really the right type, as lets you avoid InvalidCastException and instead get null you can handle.
- With reference types: as only works with reference types (classes, interfaces, delegates, arrays). You can't use it to cast value types (like int to double).
Important: If you're sure the object will always be the right type, or if you need to cast a value type, use direct casting (Type)expression. If the cast can't be done, direct casting will throw an error (InvalidCastException), which might be what you want if a wrong type is a logic bug in your program.
GO TO FULL VERSION