Author avatar

Dániel Szabó

Open and Closed Generic Types in C#

Dániel Szabó

  • Feb 24, 2020
  • 5 Min read
  • 1,518 Views
  • Feb 24, 2020
  • 5 Min read
  • 1,518 Views
Languages Frameworks and Tools
C#

Introduction

With version 2.0 of C#, a new concept was introduced called generics. The basic idea is to allow a specific type like float, double, or string to be passed as a parameter to methods, classes, or even interfaces. There is a crucial limitation to collections, which is the lack or even absence of type checking. However, this allows you to add objects of any type into a collection because they are all descendants of the object base class. This contradicts the basic definition of C# as type-safe and compromises on type safety.

In this guide, we will take a look at deeper at what generics are and what their open or closed properties mean.

Types

Types in C# have two main categories: value and reference types. Both may be generic types, which take one or more type parameters.

Closed Generics

This is one of the most powerful features of C#, helping developers define type-safe datastructures like collections. This not just results in better code quality, but behind the scenes it adds a decent performance boost as well. We'll demonstrate this with a generic Device class. This approach might be confusing, but for the demonstration's sake we are going to use string and float to describe a device. The class will have two properties, name and category. Using the float type can help us calculate costs or operation using numbers the whole time. We won't dig deeper into that part for now.

The code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;

namespace Pluralsight
{
    public class Device<T> {
        public T name { get; set; }
        public T category { get; set; }
    }
        public class Generics
    {
        public static void Main()
        {
            Device<string> server = new Device<string>();
            Device<float> another_server = new Device<float>();

            server.name = "Fabricam-DC1";
            server.category = "Domain Controller";
            another_server.name = 1.0f;
            another_server.category = 1.11f;

            Console.WriteLine($"The server: {server.name} has category: {server.category}");
            Console.WriteLine($"The server: {another_server.name} has category: {another_server.category}");
            Console.ReadKey();
        }
    }
}
csharp

The code produces the following output:

1
2
The server: Fabricam-DC1 has category: Domain Controller
The server: 1 has category: 1.11
bash

The instances created have the generic parameter T passed, which is in the first case string and the second case float. After that, when we would like to set the values for specific properties, we are only allowed to use the specific types. This is demonstrated in the assigment of name and category properties. This way, type safety is ensured by only accepting the appropriate types of parameters.

If we try to specify something else, the compiler will refuse to execute with the following message:

1
CS0029	Cannot implicitly convert type '<type>' to '<type>'	
bash

This lets us know that once we are bound to a specific type we are not allowed to assign other type.

Now that we have clarified generics, we need to make a clear distinction between open and closed generics.

Open Generics

The below code demonstrates that you can create an instance of an open type that still contains generic type parameters. Basically, when we say Generic<T>, or rather, Generic<int> or Generic<string>, we create an instance of an abstract class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;
using System.Reflection;

namespace Pluralsight
{
    public class OG<T>
    {
        public enum OGEnum
        {
            whohoo = 0
        }
    }
    public class OpenGenerics
    {
        public static object WillItRun()
            => typeof(OG<>.OGEnum).GetTypeInfo().GetDeclaredField("whohoo").GetValue(null);
        public static void Main()
        {
            object foo = WillItRun();
            Console.WriteLine($"Is this an open generic: {foo.GetType().GetTypeInfo().IsGenericTypeDefinition}");
            Console.ReadKey();
        }
    }
}
csharp

The output is as follows.

1
Is this an open generic: True
bash

The Generic<>.GenericEnum is an open type, because it is nested in an open type.

For the following code, the CLR would complain that it cannot create instances of the open type.

1
Enum.GetValues(typeof(Generic<>.GenericEnum))
csharp

By using reflection to get a constant field called whohoo the CLR automatically converts the integer into an instance of the open type.

Conclusion

In this guide, we have learned what open and closed generics are and how to use them in actual applications. Open generics are sort of a strange breed, but with proper caution they can be used to your advantage. The closed ones are what most people tend to use. I hope this guide has been informative to you and I would like to thank you for reading it!

8