Generics and Collections
C# generics and type-safe collections vs C's void* and manual data structures
Introduction
In this lesson, you'll learn about generics and collections in C#. Coming from C, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C, you're familiar with c# generics and type-safe collections vs c's void* and manual data structures.
C# has its own approach to c# generics and type-safe collections vs c's void* and manual data structures, which we'll explore step by step.
The C# Way
Let's see how C# handles this concept. Here's a typical example:
using System.Collections.Generic;
// Generic method (type-safe swap)
void Swap<T>(ref T a, ref T b) { (a, b) = (b, a); }
int x = 1, y = 2;
Swap(ref x, ref y); // x=2, y=1
// Generic class
class Box<T>
{
public T Value { get; }
public Box(T v) => Value = v;
}
// Constraints
T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
// Generic collections (type-safe dynamic arrays, maps, sets)
var nums = new List<int> { 1, 2, 3 };
nums.Add(4);
nums[2]; // 3
nums.Contains(2); // true
var dict = new Dictionary<string, int>();
dict["alice"] = 30;
dict.TryGetValue("bob", out int age); // safe get
var set = new HashSet<string>();
set.Add("a"); set.Add("a"); // dedup
set.Count; // 1
// LINQ on any collection
var evens = nums.Where(x => x % 2 == 0).ToList();
var sum = nums.Sum();Comparing to C
Here's how you might have written similar code in C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Generic via void* — no type safety
void swap(void *a, void *b, size_t size) {
char tmp[size];
memcpy(tmp, a, size);
memcpy(a, b, size);
memcpy(b, tmp, size);
}
// Dynamic array (no generics — must duplicate for each type)
typedef struct {
int *data;
int len, cap;
} IntArray;
void push(IntArray *arr, int val) {
if (arr->len == arr->cap) {
arr->cap *= 2;
arr->data = realloc(arr->data, arr->cap * sizeof(int));
}
arr->data[arr->len++] = val;
}
// Hash map would need another manual implementation
// No type safety — wrong type = runtime crashYou may be used to different syntax or behavior.
C# generics are compile-time type-safe; C's void* gives no type checking
You may be used to different syntax or behavior.
List<T> = C's IntArray but works for any T — no code duplication
You may be used to different syntax or behavior.
Dictionary<K,V> = hash table built-in; C needs manual implementation
You may be used to different syntax or behavior.
Constraints (where T : IComparable<T>) enable operations on T
You may be used to different syntax or behavior.
LINQ extends all IEnumerable<T> collections with filter/map/reduce
Step-by-Step Breakdown
1. Generic Methods
C# generic methods work for any type T while preserving type safety. No void* casting, no runtime crashes from wrong types.
void swap(void *a, void *b, size_t size) { ... }void Swap<T>(ref T a, ref T b) {
(a, b) = (b, a);
}
int x=1, y=2;
Swap(ref x, ref y); // type-safe: no void*2. List<T>
List<T> is a generic dynamic array — like C's resizing array but works for any type without code duplication.
IntArray arr; // must rewrite for each type
FloatArray arr2; // duplicate codevar nums = new List<int>();
var names = new List<string>();
// Same List<T>, different T — no duplication
nums.Add(1); names.Add("Alice");
nums[0]; // type-safe: returns int3. Dictionary<K,V>
Dictionary<K,V> is a built-in hash map. TryGetValue safely handles missing keys — no error codes.
// C: manual hash table implementation ~100 linesvar d = new Dictionary<string, int>();
d["key"] = 42;
if (d.TryGetValue("key", out int v))
Console.WriteLine(v);
d.ContainsKey("key"); // true
d.Remove("key");4. LINQ
LINQ adds functional methods to all IEnumerable<T> collections — like Python's list comprehensions but for any collection type.
// C: manual loops
int sum = 0;
for(int i=0;i<n;i++) if(arr[i]%2==0) sum+=arr[i];var evens = nums.Where(x => x % 2 == 0);
var sum = evens.Sum();
var top3 = nums.OrderByDescending(x=>x).Take(3);
var names = people.Select(p => p.Name).ToList();Common Mistakes
When coming from C, developers often make these mistakes:
- C# generics are compile-time type-safe; C's void* gives no type checking
- List<T> = C's IntArray but works for any T — no code duplication
- Dictionary<K,V> = hash table built-in; C needs manual implementation
Key Takeaways
- Generic methods <T> replace void* — type-safe at compile time
- List<T> replaces manual dynamic array — no rewrite per type
- Dictionary<K,V> replaces manual hash table; TryGetValue for safe access
- LINQ: Where/Select/OrderBy/Sum on any IEnumerable<T>