Iron Academy Logo
Learn C#

Understanding C# Abstract Class

Tim Corey
19m 59s

Abstract classes in C# are a fundamental concept that often raises questions among developers. In his video, "C# Abstract Classes - What They Are, How to Use Them, and Best Practices," Tim Corey dives into what abstract classes are, how to use them, and best practices. This article summarizes the key points from his video, using timestamps for reference.

Introduction

Tim at (0:00) explains that abstract classes are often questioned in terms of their purpose, functionality, and importance. He describes an abstract class as a blend between a full base class and an interface, situated between the two in terms of functionality.

Demo Application Walk-through

At (0:59), Tim walks through a demo application to demonstrate abstract classes. The application comprises a console application and a class library with two data access classes that simulate database operations. These classes have methods for loading and saving data, which Tim uses to illustrate the similarities and differences between abstract classes, base classes, and interfaces.

Here's the initial code for the base class and derived classes:

// Base Class Definition
public class DataAccess
{
    public string LoadConnectionString()
    {
        Console.WriteLine("Loading the connection string...");
        return "Test Connection String";
    }

    public void LoadData()
    {
        Console.WriteLine("Loading data...");
    }

    public void SaveData()
    {
        Console.WriteLine("Saving data...");
    }
}

// Derived class that inherits from DataAccess
public class SQLDataAccess : DataAccess
{
    public new void LoadData()
    {
        Console.WriteLine("Loading SQL data...");
    }

    public new void SaveData()
    {
        Console.WriteLine("Saving SQL data...");
    }
}

// Derived class that inherits from DataAccess
public class SQLLiteDataAccess : DataAccess
{
    public new void LoadData()
    {
        Console.WriteLine("Loading SQLite data...");
    }

    public new void SaveData()
    {
        Console.WriteLine("Saving SQLite data...");
    }
}
// Base Class Definition
public class DataAccess
{
    public string LoadConnectionString()
    {
        Console.WriteLine("Loading the connection string...");
        return "Test Connection String";
    }

    public void LoadData()
    {
        Console.WriteLine("Loading data...");
    }

    public void SaveData()
    {
        Console.WriteLine("Saving data...");
    }
}

// Derived class that inherits from DataAccess
public class SQLDataAccess : DataAccess
{
    public new void LoadData()
    {
        Console.WriteLine("Loading SQL data...");
    }

    public new void SaveData()
    {
        Console.WriteLine("Saving SQL data...");
    }
}

// Derived class that inherits from DataAccess
public class SQLLiteDataAccess : DataAccess
{
    public new void LoadData()
    {
        Console.WriteLine("Loading SQLite data...");
    }

    public new void SaveData()
    {
        Console.WriteLine("Saving SQLite data...");
    }
}

Creating a Base Class

Tim explains at (3:21) how to create a base class. He refactors the code to move common methods, such as LoadConnectionString, into a base class called DataAccess. By inheriting from this base class, other classes like SQLDataAccess and SQLLiteDataAccess gain access to these shared methods, reducing code duplication.

Making the Base Class Abstract

Tim at (5:56) transitions the base class into an abstract class to demonstrate the differences. He changes DataAccess to an abstract class, preventing it from being instantiated directly. Instead, only classes that inherit from this abstract class, such as SQLLiteDataAccess and SQLDataAccess, can implement its methods and use its shared functionality.

Here's how the code changes when DataAccess becomes an abstract class:

// Abstract Base Class Definition
public abstract class AbstractDataAccess
{
    public string LoadConnectionString()
    {
        Console.WriteLine("Loading the connection string...");
        return "Test Connection String";
    }

    // Abstract methods that must be implemented by derived classes
    public abstract void LoadData();
    public abstract void SaveData();
}
// Abstract Base Class Definition
public abstract class AbstractDataAccess
{
    public string LoadConnectionString()
    {
        Console.WriteLine("Loading the connection string...");
        return "Test Connection String";
    }

    // Abstract methods that must be implemented by derived classes
    public abstract void LoadData();
    public abstract void SaveData();
}

Interface Portion in the Abstract Class

Tim at (8:34) explains how abstract classes blend the features of interfaces and base classes. He declares abstract methods within the abstract class, such as public abstract void LoadData(); and public abstract void SaveData();, without implementing them. This ensures that any derived class must implement these methods, similar to how interfaces work.

Overriding Methods in Abstract Classes

Tim at (12:56) discusses how to override methods in an abstract class. He shows that you can declare a method in the base class as virtual, allowing derived classes to override it. This approach provides flexibility in how methods are implemented and extended in derived classes.

Here's the derived class code showing method overrides:

// Derived class from abstract base class
public class SQLDataAccessWithAbstract : AbstractDataAccess
{
    public override void LoadData()
    {
        Console.WriteLine("Loading SQL data...");
    }

    public override void SaveData()
    {
        Console.WriteLine("Saving SQL data...");
    }
}
// Derived class from abstract base class
public class SQLDataAccessWithAbstract : AbstractDataAccess
{
    public override void LoadData()
    {
        Console.WriteLine("Loading SQL data...");
    }

    public override void SaveData()
    {
        Console.WriteLine("Saving SQL data...");
    }
}

When to Use Abstract Classes

Tim at (16:01) advises that abstract classes should not be used every day but are valuable in specific scenarios. He cautions against using abstract classes just because two classes share similar code. Instead, he emphasizes maintaining the "is a" relationship and suggests considering helper methods or classes for shared code when appropriate.

Here's the main program code demonstrating usage:

// Main Program
class Program
{
    static void Main(string[] args)
    {
        // Using Base Class
        SQLDataAccess sqlData = new SQLDataAccess();
        Console.WriteLine(sqlData.LoadConnectionString());
        sqlData.LoadData();
        sqlData.SaveData();

        Console.WriteLine("--------------------------");

        // Using Derived Class from Abstract Base Class
        SQLDataAccessWithAbstract sqlDataAbstract = new SQLDataAccessWithAbstract();
        Console.WriteLine(sqlDataAbstract.LoadConnectionString());
        sqlDataAbstract.LoadData();
        sqlDataAbstract.SaveData();

        Console.WriteLine("--------------------------");

        // You can't instantiate Abstract Base Class directly
        // AbstractDataAccess abstractData = new AbstractDataAccess(); // Error: Cannot create an instance of the abstract class
    }
}
// Main Program
class Program
{
    static void Main(string[] args)
    {
        // Using Base Class
        SQLDataAccess sqlData = new SQLDataAccess();
        Console.WriteLine(sqlData.LoadConnectionString());
        sqlData.LoadData();
        sqlData.SaveData();

        Console.WriteLine("--------------------------");

        // Using Derived Class from Abstract Base Class
        SQLDataAccessWithAbstract sqlDataAbstract = new SQLDataAccessWithAbstract();
        Console.WriteLine(sqlDataAbstract.LoadConnectionString());
        sqlDataAbstract.LoadData();
        sqlDataAbstract.SaveData();

        Console.WriteLine("--------------------------");

        // You can't instantiate Abstract Base Class directly
        // AbstractDataAccess abstractData = new AbstractDataAccess(); // Error: Cannot create an instance of the abstract class
    }
}

Conclusion

Tim Corey's deep dive into C# abstract classes offers a clear and practical understanding of their purpose, functionality, and real-world applications. Through his demo application, he demonstrates how abstract classes bridge the gap between base classes and interfaces, enabling developers to create flexible and maintainable code structures.

By emphasizing best practices, such as using abstract classes in the right scenarios and avoiding overuse, Tim equips developers with the tools to make informed design choices. With a solid grasp of these concepts, developers can enhance their object-oriented programming skills and build robust C# applications.