BuildQuill
C# learning path
19 of 25
Lesson 19 of 25
Lesson 19Beginner18 min

C# Generics

Write reusable type-safe code without falling back to object, casts, or duplicate methods for every data type.

Before this lesson

  • Methods
  • Classes and interfaces
  • List<T> and Dictionary<TKey,TValue>

Explain the role of a type parameter

Write a small generic method and class

Use constraints only when an algorithm requires a capability

The short answer

Generics let a type or method use a type parameter supplied by its caller. List<string> and List<int> share one List<T> design while the compiler still knows the exact element type and rejects invalid values.

01

Reuse should not erase type information

Without generics, a container intended for many kinds of values could store object. Callers would need casts to retrieve specific values, and a mistaken mixture might fail only at runtime. Another approach would duplicate IntBox, StringBox, and CustomerBox even though their behavior is identical.

A generic design leaves a type-shaped blank and lets the caller fill it. In List<string>, T becomes string throughout that list instance. In Dictionary<string, decimal>, TKey becomes string and TValue becomes decimal. The compiler then preserves the same safety as a handwritten single-type version.

02

A generic method gets its type from the call

First<T> declares T after its name and uses T for the array elements and return value. When called with int[], the compiler infers T as int. The same implementation works for strings without converting either result from object.

A generic algorithm can only use operations known to be valid for every possible T. It can assign, compare with null in appropriate contexts, or call object members, but it cannot assume T has a Price property or + operator without a suitable design.

C#
static void Swap<T>(ref T left, ref T right)
{
  T temporary = left;
  left = right;
  right = temporary;
}
03

Generic types capture a reusable data structure

A generic class can retain values of T across methods. The Box<T> below wraps one value and describes it without knowing the eventual type. Real examples include collections, task results, nullable values, and result wrappers.

Do not make a type generic simply to appear flexible. If the domain concept only makes sense for Money, using T shifts useful constraints onto callers. Generic parameters should represent a real family of types that share the same algorithm or structure.

C#
class Box<T>
{
  public T Value { get; }

  public Box(T value)
  {
      Value = value;
  }

  public string Describe()
  {
      return $"Box contains: {Value}";
  }
}
04

Constraints state required capabilities

A where constraint narrows which types may be supplied and gives the generic implementation additional guarantees. where T : class requires a reference type; where T : new() requires an accessible parameterless constructor; where T : ISomeInterface requires a capability.

Use the weakest constraint that supports the operation. An interface constraint is often better than a concrete base class because it asks only for the behavior the algorithm needs. If a generic method accumulates many constraints and type checks, separate designs may be clearer.

C#
interface IHasName
{
  string Name { get; }
}

static void PrintName<T>(T item) where T : IHasName
{
  Console.WriteLine(item.Name);
}

Good habits

  • Use conventional names such as T, TKey, TValue, and TResult when their roles are familiar.
  • Use descriptive parameter names such as TMessage when several type parameters could be confused.
  • Prefer compiler-checked generic APIs over object plus repeated casts.

Quick knowledge check

Answer before you reveal.

01Does List<T> mean one list can mix every T?

No. Each constructed list has one supplied element type, such as List<int>.

02Why can a generic method not call item.Save() on unconstrained T?

The compiler has no guarantee that every possible T provides Save. Add a suitable interface constraint or redesign the operation.

Practice challenge

Now build it without copying.

Create a Pair<TFirst, TSecond> class with read-only First and Second properties. Use it for a product name and price, then for a city and population.

You are done when

  • The same class works for both pairs
  • Each retrieved property retains its exact type
  • No object casts are required

Stretch: Add a generic Describe method that accepts a formatter delegate after completing the next lesson.

Open challenge in playground

Lesson checkpoint

One small step locks it in

Mark this lesson complete, then keep the momentum going.

Complete and continue

Clear up the details

Frequently asked questions

Are generics only for collections?

No. They are used for methods, result types, services, delegates, tasks, nullable values, and many reusable algorithms.

What is covariance and contravariance?

They describe safe conversions between certain generic interface and delegate types. They are valuable later but not required to design basic generic code.

Can a generic type have several parameters?

Yes. Dictionary<TKey,TValue> and the Pair<TFirst,TSecond> exercise use separate parameters with different roles.