Though originally introduced in the programming language ML back in 1973, Generics wasn’t introduced into .NET until 2005 (.NET Framework 2.0). Since then, the usage of strongly-typed collections and other built in language features has become prevalent. This new feature makes many common tasks less prone to errors, and reduces the complexity of our code. However, pulling an object out of a collection and knowing what type it is only begins to scratch the surface of the power of Generics.
Many programs that we write every day often have common functionality that is shared between multiple semi-related classes. To reduce that duplication, we may be tempted to create helper methods that are typed directly for each of those classes. Unfortunately, we often only reduce the duplication of our code from, say, fifteen places to three. If each class or method is all doing the same thing, then it isn’t enough to perform the tasks in a single place for each type, we should continue to refactor so that the functionality exists in one method regardless of the type. Unfortunately, the most rudimentary usage of Generics doesn’t provide a way to do this, but that is easily rectified.
Before delving into detail, I want to note that the design approach I’m discussing allows for enhanced type safety and abstraction in use of Generics. However, as with all design patterns, you must judge for yourself if it fits the needs of your project.
When a Generic class or method is created, its default type is Object. This leaves the properties and methods on Object as our only means of interacting with our classes. However, we can very easily open the code to know more about what types of classes we are expecting.
For our example, we’ll start with an abstract base class for all of our Widgets. It will contain all the common information for all the items in our solution. We could have one, two, or five hundred derivations of this base, but they will all have this common information.
public abstract class Widget {
public decimal Price { get; set; }
}
Originally, our generic class declaration looks something like this:
public class WidgetService<T> {
public decimal GetTotal(T widget, int quantity) { }
}
Inside those methods, we don’t have access to the Price property, so to calculate the total we would have to cast our parameter like this:
public decimal GetTotal(T widget, int quantity) {
Widget castWidget = widget as Widget; widget.Price * quantity;
}
Besides making our code “ugly”, this opens the possibility of calling it with the wrong type. To solve this, we can add restrictions to the class definition that will specify that we want something more than just an Object to work with, as shown below.
public class WidgetService<T> where T : Widget {
public decimal GetTotal(T widget, int quantity) {
return widget.Price * quantity;
}
}
At this point, you may be asking yourself “So what? This looks exactly like something I can do with making a class and specifying those Generic parameters as the base type. That’s far simpler, and the method signatures are easier to understand.” To that, I would agree that you’re right. If that’s all that you’re doing, there isn’t much difference, and this is a little more complex. However, the difference becomes apparent when you create instances of these classes:
var service = new WidgetService<WidgetA>();
What this demonstrates for us is that within those methods, the type of T isn’t actually Widget, but WidgetA. This means that instead of returning an Object from an ArrayList, every time that T is specified within that generic class, it is the actual type that we specify. We can pass our generic widget around to other methods, or return it. In both cases, it will actually be of type WidgetA, not a base Widget, preserving our type safety.
In more complex scenarios, we may want to perform additional work on individual types of our classes, and never have a service for that base class since it will only be consumed by a specific MVC Controller, or other UI control. In those cases, we will make our base WidgetService<T> be an abstract to ensure it can never be instantiated. Then, we can expand our concrete implementations of our services with additional methods, and maintain our type safety with the base.
public abstract class WidgetService<T> where T : Widget {
protected abstract BeforeGetTotal(T widget); public decimal GetTotal(T widget, int quantity) {
BeforeGetTotal(widget); return widget.Price * quantity;
}
} public class WidgetAService : WidgetService<WidgetA> {
protected override BeforeGetTotal(WidgetA widget) {
SetDiscountPrice(widget);
} private void SetDiscountPrice(WidgetA widget) {
widget.Price *= .8;
}
} public class WidgetBService : WidgetService<WidgetB> {
protected override beforeGetTotal(WidgetB widget) {
setScalperPrice(widget);
} private void setScalperPrice(WidgetB widget) {
widget.Price *= 2.5;
}
}
An additional benefit of this approach is that we are hiding the fact that we are using generics from outside our service classes. If you don’t need to make further generic declarations on the classes that you are working with you can use this style of design to make your code cleaner and simplify the method signatures.
A final few other points to consider are that you are able to specify more concise restrictions for multiple Type parameters the same way as you declare one. Although, in C# they are comma separated before the opening curly brace.
Also, should you find yourself with a hierarchy of generic classes, you can still utilize this approach and expand on it repeatedly. Each time you derive a class from a parent, you can restrict the same Type parameter as the same, or a descendant of the one declared in its parent. The same rules of covariance apply as they do to regular method declarations.
With this powerful new tool in your arsenal, you’ll be surprised at how compact your code can become, yet still maintain its flexibility. Adding new functionality will be a snap as most of your code is already written and tested, and making any changes will be very easy as it will only need to change in one location.
For more information on what can be used with Generic constraints, see the MSDN where keyword definitions. For some additional examples of how to use Generics, see Why Use Generics by Microsoft.
Related Content
by Ben Sheppard

Ben Sheppard
Consultant
Ben Sheppard is a Full Stack .NET Architect. He specializes in reducing solution complexity through advanced refactoring to minimize development time. In his six years with Anexinet, Ben has developed complete enterprise applications for countless company clients.
© 2000 - 2021 Anexinet Corp., All rights reserved | Privacy Policy | Cookie Policy