Generics are considered an advanced and powerful idiom in any language. When I first encountered templates in C++, they seemed a little confusing. Then I read Bjarne Stroustrop's The Design and Evolution of C++, and templates seemed as easy to use as the C macros and simple string-replacement templates they supplanted. Templates and generics are the same thing—though their implementations are slightly different.
Generics in C# support defining algorithms and deferring the data type until the point of use. In early versions of C#, we arguably could live without generics because every type was derived from a common base type: object. This meant that programmers could define a Stack class based on the object type and put anything in the Stack (because everything's earliest ancestry is an object). However, a Stack of objects meant that Customer objects, Integer objects, and Foo objects could all be placed in the same Stack instance. The result was that developers subclassed data types to tighten up the type of data with which they interacted. For example, when writing custom business objects, it was a recommended practice to define strongly typed collections derived from System.Collections.CollectionBase. The reason is simple: Defining everything based on objects is considered weak typing.
The industry's best minds have believed for a couple of decades that strong typing is preferable to weak typing, so it seems natural that .NET eventually had to support strong typing. Strongly typed algorithms naturally suggest parameterized types, which is where we are with generics.
For more than a decade, we've used the letter T as the name for parameterized types. Thus, anywhere the data type is meant to be provided by the generic class consumer, you'll find a T. The key to using generics is simply to provide the T. The key to defining generics is to implement a method or class and replace specific data types with T.
Generics in C# support some additional refinements. For example, a method or class can have more than one parameterized type, and generics in C# support WHERE constraints that refine the type of the parameterized types. For example, if a generic type must implement IDisposable, C# generics support expressing this limitation. We'll look at constraints later in this article.
Without further ado, let's jump in.