PY
C#

Python to C#

10 lessons

Progress0%
1Variables & Types2Classes & OOP3Collections & LINQ4Async/Await5Exception Handling6File I/O7Generics8Delegates and Events9Records and Pattern Matching10Interfaces
All Mirror Courses
PY
C#
Generics
MirrorLesson 7 of 10
Lesson 7

Generics

Type parameters, constraints, and generic collections

Introduction

In this lesson, you'll learn about generics in C#. Coming from Python, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
PY
From Python:

In Python, you're familiar with type parameters, constraints, and generic collections.

C#
In C#:

C# has its own approach to type parameters, constraints, and generic collections, which we'll explore step by step.

The C# Way

Let's see how C# handles this concept. Here's a typical example:

C#
C# Example
// Generic method
T First<T>(IList<T> items) => items[0];

// Generic class
public class Stack<T>
{
    private List<T> _items = new();
    public void Push(T item) => _items.Add(item);
    public T Pop()
    {
        var v = _items[^1];
        _items.RemoveAt(_items.Count - 1);
        return v;
    }
    public int Count => _items.Count;
}

// Constraints
T Max<T>(T a, T b) where T : IComparable<T>
    => a.CompareTo(b) >= 0 ? a : b;

// Multiple constraints
void Save<T>(T entity)
    where T : class, IEntity, new()
{ }

// Generic interface
public interface IRepository<T> where T : class
{
    T? GetById(int id);
    void Save(T entity);
    IEnumerable<T> GetAll();
}

// Usage
var s = new Stack<int>();
s.Push(1);
Console.WriteLine(s.Pop());  // 1
Max(3, 7);                    // 7

Comparing to Python

Here's how you might have written similar code in Python:

PY
Python (What you know)
from typing import TypeVar, Generic, List

T = TypeVar("T")

# Generic function
def first(items: List[T]) -> T:
    return items[0]

# Generic class
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: List[T] = []
    def push(self, item: T) -> None:
        self._items.append(item)
    def pop(self) -> T:
        return self._items.pop()

# Bounded TypeVar
from typing import SupportsFloat
N = TypeVar("N", bound=SupportsFloat)

def total(items: List[N]) -> float:
    return sum(float(x) for x in items)

s: Stack[int] = Stack()
s.push(1)
print(s.pop())  # 1
Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

C# generics are reified — typeof(T) works at runtime (unlike Java's type erasure)

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

Constraints use 'where T : ...' — similar to Python's TypeVar(bound=...)

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

where T : class = reference type; where T : struct = value type

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

where T : new() requires parameterless constructor — Python has no equivalent

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

C# infers T from method arguments — rarely need to specify explicitly

Step-by-Step Breakdown

1. Generic Methods

Declare <T> after the method name. C# infers T from arguments automatically in most cases.

PY
Python
T = TypeVar("T")
def wrap(v: T) -> dict[str, T]:
    return {"value": v}
C#
C#
Dictionary<string,T> Wrap<T>(T v)
    => new() { ["value"] = v };
// Caller: Wrap("hello") — T inferred as string

2. Generic Classes

Generic classes are parameterized at instantiation. C# generics are reified — the runtime knows the actual type T.

PY
Python
class Box(Generic[T]):
    def __init__(self, v: T): self.value = v
C#
C#
class Box<T> {
    public T Value { get; }
    public Box(T v) => Value = v;
}
var b = new Box<string>("hi");
Console.WriteLine(b.Value.GetType()); // String

3. Type Constraints

Constraints let you call interface methods on T. 'where T : IComparable<T>' allows using CompareTo().

PY
Python
N = TypeVar("N", bound=SupportsFloat)
def sum_all(items: list[N]) -> float: ...
C#
C#
double SumAll<T>(IList<T> items)
    where T : IConvertible
    => items.Sum(x => x.ToDouble(null));

4. Generic Interfaces

Generic interfaces define contracts that work for any type. IRepository<T> is a common pattern for data access.

PY
Python
from typing import Protocol
class Repository(Protocol[T]):
    def get(self, id: int) -> T: ...
C#
C#
interface IRepo<T> where T : class {
    T? GetById(int id);
    void Save(T entity);
    IEnumerable<T> GetAll();
}
Rule of Thumb
Use generic interfaces for dependency injection — it makes swapping implementations easy.

Common Mistakes

When coming from Python, developers often make these mistakes:

  • C# generics are reified — typeof(T) works at runtime (unlike Java's type erasure)
  • Constraints use 'where T : ...' — similar to Python's TypeVar(bound=...)
  • where T : class = reference type; where T : struct = value type
Common Pitfall
Don't assume C# works exactly like Python. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • Generic methods: T Method<T>(T arg); classes: class Foo<T>
  • Constraints (where): IComparable<T>, class, struct, new() enable operations on T
  • C# generics are reified — typeof(T) and GetType() work at runtime
  • Generic interfaces (IRepository<T>) are the foundation of clean architecture patterns
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your Python code in C# to practice these concepts.
PreviousNext