Generics in C# (List<T>, Dictionary<TKey,TValue>)
Learn generics in C#, including List<T> and Dictionary<TKey,TValue>, to write type-safe and reusable code with examples.
In C#, generics are used to increase type safety and flexibility. Generic classes and collections eliminate the need for runtime type casting and avoid unnecessary boxing and unboxing operations, improving performance and reducing the risk of errors. Before generics were introduced, collections such as ArrayList stored elements as object. When a value type like int was added to an ArrayList, it had to be boxed (converted to object), and later unboxed when retrieved. This boxing/unboxing process caused additional memory allocations and performance overhead. With generic collections like List<T> and Dictionary<TKey,TValue>, elements are stored in their actual types, eliminating these costs and making the code both safer and faster.
List<T>
List<T> is a generic collection that dynamically stores elements of a specific type.
T specifies the type of elements the list will contain.
using System;
using System.Collections.Generic;
var numbers = new List<int>();
numbers.Add(10);
numbers.Add(20);
numbers.Add(30);
foreach (int n in numbers)
Console.WriteLine(n);
// Output:
// 10
// 20
// 30
Note: Unlike arrays, List<T> can grow and shrink dynamically.
Dictionary<TKey,TValue>
Dictionary is a generic key–value collection.
TKey specifies the type of the key, and TValue specifies the type of the value.
using System;
using System.Collections.Generic;
var students = new Dictionary<int, string>();
students[101] = "John";
students[102] = "Mary";
students[103] = "Michael";
foreach (var kv in students)
Console.WriteLine($"{kv.Key} → {kv.Value}");
// Output:
// 101 → John
// 102 → Mary
// 103 → Michael
Note: Each Key in a dictionary must be unique.
Attempting to add the same key again will cause an error.
Creating Your Own Generic Classes
You can also define your own generic types, not just use built-in collections. This allows you to create reusable and type-safe structures.
public class Box<T>
{
public T Value { get; set; }
public void Print()
{
Console.WriteLine($"Box contains: {Value}");
}
}
class Program
{
static void Main()
{
var intBox = new Box<int> { Value = 42 };
intBox.Print();
var stringBox = new Box<string> { Value = "Hello" };
stringBox.Print();
}
}
// Output:
// Box contains: 42
// Box contains: Hello
Generic Constraints
Generic types can use constraints (with the where keyword)
to enforce certain requirements on the type parameters.
public class Repository<T> where T : class
{
private readonly List<T> _items = new();
public void Add(T item) => _items.Add(item);
public void PrintAll()
{
foreach (var i in _items)
Console.WriteLine(i);
}
}
class Program
{
static void Main()
{
var repo = new Repository<string>();
repo.Add("Pen");
repo.Add("Notebook");
repo.PrintAll();
}
}
// Output:
// Pen
// Notebook
Sample Application: Product Management
In the following example, product information is stored inside a List<Product>,
and product ID–name mappings are kept inside a Dictionary<int,string>.
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
class Program
{
static void Main()
{
var products = new List<Product>
{
new Product { Id = 1, Name = "Laptop" },
new Product { Id = 2, Name = "Phone" }
};
var productDict = new Dictionary<int, string>();
foreach (var p in products)
productDict[p.Id] = p.Name;
Console.WriteLine("Products:");
foreach (var kv in productDict)
Console.WriteLine($"{kv.Key} - {kv.Value}");
}
}
// Output:
// Products:
// 1 - Laptop
// 2 - Phone
LINQ alternative: You can also convert the list directly into a dictionary with ToDictionary.
var productDict = products.ToDictionary(p => p.Id, p => p.Name);
Console.WriteLine("Products:");
foreach (var kv in productDict)
Console.WriteLine($"{kv.Key} - {kv.Value}");
TL;DR
- Generics improve type safety and reusability.
List<T>: Dynamic, type-safe list.Dictionary<TKey,TValue>: Fast lookup with unique keys.- You can create your own generic classes and enforce type constraints.
- Generics are performant and reduce the need for boxing/unboxing.
Related Articles
Arrays in C#
Learn arrays in C#, including declaration, indexing, looping through elements, and common array operations with examples.
C# Type Conversions
Learn how type conversions work in C#, including implicit and explicit casting, Parse, TryParse, and Convert methods with examples.
Collections in C#: List, Dictionary, Queue, Stack
Learn C# collections like List, Dictionary, Queue, and Stack to store and manage data efficiently with practical examples.