Skip to content

Generics

Generic Classes

class Box[T]:
    """A container for a single value."""
    _value: T

    def __init__(self, value: T):
        self._value = value

    def get(self) -> T:
        return self._value

    def set(self, value: T):
        self._value = value

# Usage
int_box = Box[int](42)
str_box = Box[str]("hello")

Implementation - ✅ Native - class Box<T>

Generic Functions

def identity[T](value: T) -> T:
    return value

def first[T](items: list[T]) -> T:
    return items[0]

Implementation - ✅ Native - T Identity<T>(T value)

Generic Function Instantiation

Generic functions support both type inference and explicit type arguments at call sites:

Type inference (preferred):

# Type inferred from argument
result = identity(42)           # T inferred as int
name = identity("hello")        # T inferred as str

items: list[int] = [1, 2, 3]
x = first(items)                # T inferred as int from list[int]

Explicit type arguments:

# Explicitly specify type parameter
result = identity[int](42)      # T explicitly int
name = identity[str]("hello")   # T explicitly str

# Useful when inference is ambiguous or impossible
empty = create_empty_list[int]()  # No argument to infer from

Multiple type parameters:

def convert[T, U](value: T, converter: (T) -> U) -> U:
    return converter(value)

# Explicit type arguments for multiple parameters
result = convert[str, int]("42", int.parse)

# Can also infer from arguments
result = convert("42", int.parse)  # T=str, U=int inferred

Partial type argument specification is not supported:

# ❌ Cannot specify only some type parameters
result = convert[str](...)  # ERROR: must specify all or none

Implementation - ✅ Native - identity<int>(42) in C# - Type arguments use [] in Sharpy, lowered to <> in C#

Type Constraints

interface IComparable[T]:
    def __lt__(self, other: T) -> bool: ...

def find_max[T: IComparable[T]](items: list[T]) -> T:
    """Find the maximum item (must be comparable)."""
    max_item = items[0]
    for item in items:
        if max_item < item:
            max_item = item
    return max_item
Constraint C# Equivalent
T: Interface where T : Interface
T: class where T : class
T: struct where T : struct

Implementation - ✅ Native - Direct mapping to C# generic constraints.

Multiple Type Constraints

A type parameter can have multiple constraints using the & syntax:

# Single constraint
def compare[T: IComparable[T]](a: T, b: T) -> int:
    return a.compare_to(b)

# Multiple constraints with &
def sort_and_hash[T: IComparable[T] & IHashable](items: list[T]) -> int:
    """Sort items and return combined hash."""
    sorted_items = sorted(items)
    return hash(tuple(sorted_items))

# Multiple constraints on class
class SortedSet[T: IComparable[T] & IEquatable[T]]:
    _items: list[T]

    def add(self, item: T):
        # Can use both comparison and equality
        pass

Constraint combinations:

Sharpy Syntax C# Equivalent
T: IFoo where T : IFoo
T: IFoo & IBar where T : IFoo, IBar
T: class & IFoo where T : class, IFoo
T: struct & IFoo where T : struct, IFoo
T: class & IFoo & IBar where T : class, IFoo, IBar

For variance annotations on type parameters (out T for covariance, in T for contravariance), see Generic Variance.

Order matters for class/struct:

When combining class or struct with interface constraints, class/struct should come first:

# ✅ Preferred: class/struct first
def process[T: class & IDisposable](item: T): ...

# ✅ Also valid: interfaces only
def process[T: IDisposable & ICloneable](item: T): ...

# The compiler reorders constraints to match C# requirements

Implementation - ✅ Native - & constraints lower to comma-separated C# where clause - T: IFoo & IBarwhere T : IFoo, IBar