Learning outcome
By the end of this lesson, you should be able to explain the difference between a generic type and a generic method, recognize when each is the better fit, and describe how the compiler uses type arguments and type inference to keep code both reusable and statically safe.
Intuition
Generics answer a common design problem: you want one algorithm, but you do not want to rewrite it for int, string, Guid, or your own domain types. In C#, a generic type or method introduces one or more type parameters such as T, TKey, or TValue. The compiler fills in those placeholders with real types at compile time, which means you keep strong typing and avoid casts.
Think of a generic type as a reusable container or abstraction whose shape depends on a type argument. List<T> is the classic example: List<int> and List<string> are both lists, but their element types are fixed and checked by the compiler. A generic method, by contrast, is a single operation that can work across types. For example, a swap routine or a formatter can often be expressed once as Swap<T> or Print<T>.
The key idea is that generics give you reuse without falling back to object. That matters because object forces boxing for value types and often reintroduces casts and runtime failures. With generics, invalid combinations are rejected before the program runs.
Deep dive
A generic method declares its own type parameter list after the method name. The compiler can often infer the type argument from the method call, so you do not always need to write angle brackets explicitly. For example, when a method accepts T value, calling it with 42 lets the compiler infer T as int.
A generic type declares its type parameters after the type name. Every member of that type can then use the class-level type argument. This is why List<T> can expose Add(T item) and T this[int index] without repeating the element type everywhere.
Constraints narrow what a type parameter is allowed to be. They are important because a bare T only guarantees operations available on object. If a method needs comparison, it can require where T : IComparable<T>. That lets the method call CompareTo while still staying generic.
A useful mental model is:
- Use a generic type when the variation belongs to the whole abstraction, such as a collection, cache, repository, or option wrapper.
- Use a generic method when only a single operation needs to vary by type.
A generic method can also live inside a generic type. When that happens, keep the type parameter names distinct. Reusing the same identifier such as T for both the type and the method is confusing and can trigger warnings about hiding. Prefer clearer names like TItem, TValue, or U.
Here is the core shape of generic methods in C#:
And here is the matching idea for a generic type:
Failure modes
The most common mistake is treating generics like a way to avoid thinking about types. Generics are not “any type, do whatever you want”; they are “any type that satisfies the contract.” If your generic algorithm needs ordering, equality, construction, or a specific interface, state that requirement with a constraint.
Another common mistake is assuming the compiler can infer a type argument from a return value alone. In practice, inference is driven by the supplied arguments. If a generic method has no parameters, you may need to specify the type argument explicitly.
Also avoid using object as a fake generic mechanism. It loses compile-time safety and often causes boxing or cast failures later. Likewise, avoid inventing separate SwapInt, SwapString, and SwapGuid overloads when one generic method would do the job better.
Interview drill
Be ready to answer these questions:
- What is the difference between
List<T>andSwap<T>? - Why is
List<object>not a substitute forList<int>? - When can the compiler infer
Tautomatically, and when can it not? - Why do constraints exist, and what do they enable?
- What problem does generics solve beyond code reuse?
A concise interview answer might be: generics let you write one algorithm or one data structure that works across many types while preserving compile-time checking, better performance for value types, and cleaner APIs.
Revision checklist
- I can define a generic type and a generic method.
- I can explain type inference for generic methods.
- I can explain why constraints are needed.
- I can choose between
objectandT, and explain whyTis better. - I can identify when to place the type parameter on the type versus on the method.
- I can describe why generics improve both safety and reuse.
Production code
The following console example demonstrates both a generic type and generic methods. It uses a generic Box<T> type, a generic Swap<T> method, and a constrained Max<T> method. The output proves that the same algorithm works for multiple types while remaining strongly typed.
Code walkthrough
Box<T> is a generic type. The T placeholder is fixed when the caller chooses Box<int> or Box<string>. That means the compiler knows exactly what Value contains in each case.
Swap<T> is a generic method. The algorithm is written once, but it works for any type that can be assigned and moved through a temporary variable. The compiler infers T from the ref arguments, so calls such as Algorithms.Swap(ref a, ref b) stay concise.
Max<T> shows a constraint. Without where T : IComparable<T>, the method would not be allowed to call CompareTo. The constraint lets the method remain reusable while still supporting a meaningful operation.
Notice how the example never casts. That is the real benefit of generics: the API stays flexible, but the compiler still protects you from mixing incompatible types.
For version-sensitive reference material, see the official C# generic methods guide and the generic types fundamentals page:
Executable code examples
Generic type and generic method example
Program.cs
using System;
public sealed class Box<T>
{
public T Value { get; }
public Box(T value)
{
Value = value;
}
}
public static class Algorithms
{
public static void Swap<T>(ref T left, ref T right)
{
T temp = left;
left = right;
right = temp;
}
public static T Max<T>(T first, T second) where T : IComparable<T>
{
return first.CompareTo(second) >= 0 ? first : second;
}
}
public static class Program
{
public static void Main()
{
var numberBox = new Box<int>(42);
var textBox = new Box<string>("hello");
int a = 10;
int b = 20;
Algorithms.Swap(ref a, ref b);
string left = "pear";
string right = "apple";
Algorithms.Swap(ref left, ref right);
int largerNumber = Algorithms.Max(12, 7);
string laterWord = Algorithms.Max("ant", "zebra");
Console.WriteLine($"Box<int>: {numberBox.Value}");
Console.WriteLine($"Box<string>: {textBox.Value}");
Console.WriteLine($"Swapped ints: {a}, {b}");
Console.WriteLine($"Swapped strings: {left}, {right}");
Console.WriteLine($"Max int: {largerNumber}");
Console.WriteLine($"Max string: {laterWord}");
}
}