In Part 1, we created a base class that contains a common property in our app, UserId. Before we move on, let’s tack on some common functions we can use throughout our app. If we bundle up some lightweight methods in a Utilities class, we can attach that to our base class as a read-only property.

We can take our new class and attach it to the base class with one line of code. We’ll need to add a constructor to our Initialize method, too.

I am sure there are detractors to this approach. I like it because I can code one reference and have my functions available in any derived class. However, I only do this with lightweight classes with methods that consume simple types, structs, enums or other lightweight classes. We’ll use the functions in this example in later articles. For now, though, we will consider this our completed super class.

Let’s go to our main class library and derive a new abstract class from the LevelOneBase class. If we’re only creating another abstract, or base, class, why not just include everything in the original base class? The reason is that, by layering abstract classes, we can propagate only the functionality required by the level we are at in our class hierarchy. As an example, our program manages tasks and tracks the result of running a series of tasks. We will create a new abstract class whose role is to manage tasks and call it TaskManager.

When you follow a new declared class by a colon and another class name, the new class inherits the other class. That means we can access the UserId property without declaring it a second time and also be able to access our utility functions. Note how when parameters are provided in the constructor, the Initialize method is called rather than setting properties right in the constructor. This is done so that logic is coded once. If we add other limitations to the container size later, we only need to update the Initialize method. This also supports the requirement that the tree must pass on initialization to the base classes.

We added a TaskCollection property to the TaskManager class to hold a collection of running tasks. One of the best parts of using async and await in C# ( is that you can work, not only with class objects, but with running tasks as well. In Part 3 we will create a new method that processes these tasks and also extend our class with anonymous types as a way to manage tasks with different return types using one base class and method.