Generic types were introduced to C# 2 in 2005. Since 2005, Generics have become a pivotal part of C#, empowering things like Lists and Async/Await. If you started learning or using C# after version 2, you may be using Generics on a daily basis without really knowing what they are. This series (Part Two, Part Three) will try to bring some clarity to Generics in C#.
One common use of Generics is in List<T>. Where <T> specifies a Type Parameter.
Before diving into Generics, it would be helpful to think about what kind of problem they solve. The best way to do this is to look back at C# 1. Before Generics, C# had Arrays and ArrayLists.
To declare an array, you must specify the type of the array and the number of elements it will contain. Declaring the type is very beneficial, having strong types is one of the benefits of C#, but declaring the size up front can be problematic. This can lead to a lot of repetitive code to manage the size of the array as you create new bigger arrays and copy the old values into it, and don't forget at the end of your method to shrink the array down to the final size.
ArrayLists are resizable, so they solve that problem, but they accept anything of type Object. Which means to work with the values inside of an ArrayList requires casting or boxing.
Lists in the System.Collections.Generic namespace give us the best of both worlds. Lists are strongly typed and resizable. To declare a List, an argument must be passed into the Type parameter (Type parameters are surrounded by < > symbols). This argument determines the type of the List. Once declared, only objects of that type are allowed to be added to the List.
Once declared as a List<int> we cannot Add() any other type to the list. This strong typing is where the real power of Generics shines through.
Lists also dynamically resize. Simply calling the Add() or AddRange() methods will dynamically resize the list as needed to fit the number of elements.
Continue to Part Two to learn how to write your own Generic methods.