At the end of the previous lesson a problem was left pending: Book already has Title,
Author, Available, Lend(), Return() and ShowDetails() perfectly defined, but if
BiblioTech wanted to add magazines to the catalog too, you'd have to create a Magazine
class by copying and pasting those exact same properties and methods, changing only what's
specific to a magazine (for example, its issue number). That duplication is exactly what
inheritance avoids: it lets you define a base class with what several types have in
common, and have other classes inherit those properties and methods automatically, adding
only what sets them apart. In this lesson you'll extract a base class, LibraryItem, from
Book, from which both Book and a new Magazine class will inherit.
Contents
- The problem: duplicated properties and methods
- Inheriting with
:— theLibraryItembase class Bookinherits fromLibraryItem- The
baseoperator for calling the base class constructor - Adding
Magazineas a second inheriting class - Overriding inherited members with
virtualandoverride - Sealed classes with
sealed - Single inheritance hierarchy in C#
- The problem: duplicated properties and methods
Recall the Book class as it stood at the end of the previous lesson:
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public string Isbn { get; set; }
public bool Available { get; set; } = true;
public Book(string title, string author, string isbn)
{
Title = title;
Author = author;
Isbn = isbn;
}
public void Lend() { /* ... */ }
public void Return() { /* ... */ }
public void ShowDetails() { /* ... */ }
}If you now wrote a Magazine class from scratch, with Title, Author, Available,
Lend(), Return() and ShowDetails() practically identical, and only Isbn replaced by,
say, IssueNumber, you'd be duplicating a considerable amount of code. And worse: if you
later fixed a bug in Lend(), you'd have to remember to fix it in the Magazine copy too.
Inheritance solves this problem at its root.
- Inheriting with
: — the LibraryItem base class
: — the LibraryItem base classThe first step is identifying what a book and a magazine have in common within BiblioTech:
both have a title, an author, and can be either available or lent out. That common part is
extracted into a new class, LibraryItem, which will act as the base class:
class LibraryItem
{
public string Title { get; set; }
public string Author { get; set; }
public bool Available { get; set; } = true;
public LibraryItem(string title, string author)
{
Title = title;
Author = author;
}
public void Lend()
{
if (Available)
{
Available = false;
Console.WriteLine($"'{Title}' has been lent out.");
}
else
{
Console.WriteLine($"'{Title}' is not available for loan.");
}
}
public void Return()
{
Available = true;
Console.WriteLine($"'{Title}' has been returned.");
}
public void ShowDetails()
{
Console.WriteLine($"Title: {Title}");
Console.WriteLine($"Author: {Author}");
Console.WriteLine($"Available: {Available}");
}
}LibraryItem isn't a concept meant to be used "on its own" in BiblioTech — you'll never
create an object that is "a library item" and nothing more — but rather the base on which more
concrete concepts like Book or Magazine are built. This idea will come up again in the
Abstraction lesson, later in the module.
Book inherits from LibraryItem
Book inherits from LibraryItemFor a class to inherit from another, you write a colon (:) after the class name, followed by
the base class name:
class Book : LibraryItem
{
public string Isbn { get; set; }
public Book(string title, string author, string isbn) : base(title, author)
{
Isbn = isbn;
}
}class Book : LibraryItem means "Book is a LibraryItem, in addition to having its own
particular features." Thanks to inheritance, Book automatically gets all the properties
(Title, Author, Available) and methods (Lend(), Return(), ShowDetails()) from
LibraryItem, without rewriting them, and only needs to declare what's specific to it:
Isbn.
Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(book1.Title); // Hopscotch (inherited from LibraryItem)
Console.WriteLine(book1.Isbn); // 978-84-376-0495-4 (own to Book)
book1.Lend(); // 'Hopscotch' has been lent out. (inherited)
book1.ShowDetails(); // inherited, shows Title/Author/Available
- The
base operator for calling the base class constructor
base operator for calling the base class constructorIn the Book constructor from the previous section, : base(title, author) appears after the
constructor's signature. The base keyword lets you explicitly invoke a member of the
base class — in this case, its constructor — from the inheriting class.
This is necessary because LibraryItem requires title and author in its own constructor
(it doesn't have a parameterless constructor), so any class that inherits from it must
explicitly state how that inherited part gets built. The execution order is always: first the
base class constructor runs (LibraryItem(title, author), which assigns Title and
Author), and then the body of Book's constructor (which assigns Isbn).
base can also be used inside a method to invoke the base class's version of that same
method (you'll see this in section 6, with ShowDetails()), not just in constructors.
- Adding
Magazine as a second inheriting class
Magazine as a second inheriting classWith LibraryItem already defined, adding Magazine to BiblioTech's catalog is now much
simpler: all that's needed is what sets it apart from a book, which in this case is its issue
number.
class Magazine : LibraryItem
{
public int IssueNumber { get; set; }
public Magazine(string title, string author, int issueNumber) : base(title, author)
{
IssueNumber = issueNumber;
}
}Magazine magazine1 = new Magazine("National Geographic", "Various authors", 302);
magazine1.ShowDetails(); // inherited: Title, Author, Available
Console.WriteLine(magazine1.IssueNumber); // 302 (own to Magazine)
magazine1.Lend(); // inherited, works the same as in BookBook and Magazine share all the logic for Title, Author, Available, Lend() and
Return() without having written it twice: both inherit from the same base class
LibraryItem, and each adds only its own particular feature (Isbn in one case,
IssueNumber in the other).
- Overriding inherited members with
virtual and override
virtual and overrideBy default, an inherited method behaves exactly the same in the inheriting class as in the
base class: book1.ShowDetails() and magazine1.ShowDetails() literally run the same code
from LibraryItem. But it makes sense for a book's details to also include its ISBN, and a
magazine's, its issue number — information LibraryItem doesn't know about. To let an
inheriting class override the behavior of an inherited method, the base class must mark
that method as virtual, and the inheriting class must mark its own version as override:
class LibraryItem
{
// ... properties and constructor as before ...
public virtual void ShowDetails()
{
Console.WriteLine($"Title: {Title}");
Console.WriteLine($"Author: {Author}");
Console.WriteLine($"Available: {Available}");
}
}
class Book : LibraryItem
{
public string Isbn { get; set; }
public Book(string title, string author, string isbn) : base(title, author)
{
Isbn = isbn;
}
public override void ShowDetails()
{
base.ShowDetails(); // runs the LibraryItem version first
Console.WriteLine($"ISBN: {Isbn}"); // and adds Book's own line
}
}Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
book1.ShowDetails();
// Title: Hopscotch
// Author: Julio Cortazar
// Available: True
// ISBN: 978-84-376-0495-4Inside Book.ShowDetails(), the call base.ShowDetails() runs the original version of the
method defined in LibraryItem (the first three lines), and then the line specific to the
ISBN is added. This avoids duplicating the Title, Author and Available lines: the base
class's implementation is reused, and only what's new is added. Magazine would do exactly
the same with its own IssueNumber:
class Magazine : LibraryItem
{
public int IssueNumber { get; set; }
public Magazine(string title, string author, int issueNumber) : base(title, author)
{
IssueNumber = issueNumber;
}
public override void ShowDetails()
{
base.ShowDetails();
Console.WriteLine($"Issue number: {IssueNumber}");
}
}| Keyword | Where it's used | What it means |
|---|---|---|
virtual |
On the base class's method | "Inheriting classes may override this method" |
override |
On the inheriting class's method | "This is a class's own version of a method marked virtual in the base" |
base.Method() |
Inside an overridden method | "Also run the original version of the method, defined in the base class" |
This lesson has focused on the mechanics of inheriting and overriding; in the next lesson, Polymorphism, you'll see why this ability to override methods is much more powerful than it seems at first glance, when combined with lists and arrays of the base class.
- Sealed classes with
sealed
sealedSometimes you want to explicitly prevent a class from having any inheritors, for example
because its design isn't meant to be extended further. For that, use the sealed keyword
before class:
sealed class Magazine : LibraryItem
{
public int IssueNumber { get; set; }
public Magazine(string title, string author, int issueNumber) : base(title, author)
{
IssueNumber = issueNumber;
}
public override void ShowDetails()
{
base.ShowDetails();
Console.WriteLine($"Issue number: {IssueNumber}");
}
}
// class DigitalMagazine : Magazine { }
// Compilation error: cannot inherit from a sealed classMarking Magazine as sealed makes it clear, for anyone reading the code, that it isn't
meant to be a new base class: it's an end point in that branch of the hierarchy.
LibraryItem and Book, on the other hand, remain without sealed for the rest of the
module, since LibraryItem needs to stay inheritable by design.
- Single inheritance hierarchy in C#
An important — and deliberate — limitation of C# is that a class can only inherit from one base class: there's no multiple inheritance of classes, unlike other aspects of the language (such as interfaces, which can indeed be combined several at a time, and which you'll see in Module 4). This means BiblioTech's inheritance hierarchy always forms a tree, never a network:
classDiagram
LibraryItem <|-- Book
LibraryItem <|-- Magazine
class LibraryItem {
+string Title
+string Author
+bool Available
+Lend()
+Return()
+ShowDetails()
}
class Book {
+string Isbn
+ShowDetails()
}
class Magazine {
+int IssueNumber
+ShowDetails()
}
All classes in C# — including LibraryItem, even though you didn't write it explicitly —
ultimately inherit from a common root class called object, from which methods like
ToString() come, which you've already used indirectly when interpolating objects into
strings. This is beyond the scope of this lesson, but it's worth knowing that C#'s class
hierarchy always has that common root at the top.
Common Mistakes and Tips
- Forgetting
base(...)when the base class has no parameterless constructor: ifLibraryItemonly definesLibraryItem(string title, string author), any inheriting class is required to state: base(...)with those arguments; if not, the compiler gives an error. - Overriding without
override(or withoutvirtualin the base): if the base class's method isn'tvirtual, the inheriting class can't useoverride; and if instead you try to "hide" the method with a different name unrelated to real overriding (usingnewin the method's signature), the resulting behavior tends to be confusing and rarely what was intended. Always usevirtual/overridewhen the intent is to override. - Duplicating code that's already in the base class: if
BookandMagazineredefineTitle,AuthororAvailableon their own, the advantage of inheritance is lost and the problem this lesson solves is reintroduced. - Marking a class
sealedthat you actually need to keep extending:sealedis a deliberate design decision; apply it only when you're certain that class shouldn't have any more inheritors (likeMagazinein this module), not out of habit. - Expecting multiple inheritance of classes: in C# a class can only inherit from a single base class; if you need to combine several independent capabilities, the right tool is interfaces, which you'll see in Module 4.
Exercises
-
Define the
LibraryItemclass with the propertiesTitleandAuthor(string) andAvailable(bool, with a property initializer set totrue), a constructorLibraryItem(string title, string author), and a methodvoid ShowDetails()that prints the three properties to the console. -
Create the
Book : LibraryItemclass with the additional propertyIsbn(string) and a constructorBook(string title, string author, string isbn)that usesbase(...)to initialize the inherited part. Create aBookobject and print itsTitle(inherited) and itsIsbn(own). -
Create the
Magazine : LibraryItemclass (withoutsealedfor now) with the propertyIssueNumber(int) and its own constructor withbase(...). MarkShowDetails()asvirtualinLibraryItemand override it inMagazinewithoverride, so that, besides the inherited details, it also shows the issue number usingbase.ShowDetails().
Solutions
class LibraryItem
{
public string Title { get; set; }
public string Author { get; set; }
public bool Available { get; set; } = true;
public LibraryItem(string title, string author)
{
Title = title;
Author = author;
}
public void ShowDetails()
{
Console.WriteLine($"Title: {Title}");
Console.WriteLine($"Author: {Author}");
Console.WriteLine($"Available: {Available}");
}
}
class Book : LibraryItem
{
public string Isbn { get; set; }
public Book(string title, string author, string isbn) : base(title, author)
{
Isbn = isbn;
}
}
Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(book1.Title); // Hopscotch
Console.WriteLine(book1.Isbn); // 978-84-376-0495-4
class LibraryItem
{
public string Title { get; set; }
public string Author { get; set; }
public bool Available { get; set; } = true;
public LibraryItem(string title, string author)
{
Title = title;
Author = author;
}
public virtual void ShowDetails()
{
Console.WriteLine($"Title: {Title}");
Console.WriteLine($"Author: {Author}");
Console.WriteLine($"Available: {Available}");
}
}
class Magazine : LibraryItem
{
public int IssueNumber { get; set; }
public Magazine(string title, string author, int issueNumber) : base(title, author)
{
IssueNumber = issueNumber;
}
public override void ShowDetails()
{
base.ShowDetails();
Console.WriteLine($"Issue number: {IssueNumber}");
}
}
Magazine magazine1 = new Magazine("National Geographic", "Various authors", 302);
magazine1.ShowDetails();
// Title: National Geographic
// Author: Various authors
// Available: True
// Issue number: 302
Conclusion
In this lesson you've learned to avoid code duplication by extracting a base class,
LibraryItem, from which Book and the new Magazine inherit via :; you've used base to
invoke the base class's constructor (and its method), you've overridden inherited members with
virtual/override, you've seen how sealed prevents a class from having inheritors, and
why C# only allows inheriting from a single base class at a time. BiblioTech's domain model
now has a real hierarchy: LibraryItem at the base, with Book and Magazine as concrete
specializations.
However, you haven't yet tapped into the real power of this hierarchy: being able to treat a
Book and a Magazine uniformly, as if both were simply "a LibraryItem," and letting each
one behave according to its own type at runtime. That's exactly polymorphism, the topic of
the next lesson.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
- Arrays and Strings
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- Structs and Records: Value Types and Reference Types
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Pattern Matching and Modern C# Features
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
- File I/O
- Serialization
- Database Connectivity
- Entity Framework
- Working with JSON and Consuming REST APIs
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming
Module 7: Building Applications
Module 8: Best Practices and Design Patterns
- Coding Standards and Best Practices
- Design Patterns
- Dependency Injection and Inversion of Control
- Unit Testing
- Code Review and Refactoring
