C#
02 / 07

OOP & Type System

C#: OOP & Type System

Classes & Inheritance

// Class with properties and constructor
public class Animal
{
    public string Name { get; init; }       // init-only — set only in constructor/initializer
    public int Age { get; private set; }    // public read, private write
    protected string Species { get; set; }

    private static int _count;
    public static int Count => _count;

    public Animal(string name, int age, string species)
    {
        Name = name;
        Age = age;
        Species = species;
        _count++;
    }

    // Virtual — can be overridden
    public virtual string Describe() => $"{Species} named {Name}";

    // Sealed — prevent inheritance of this method
    public sealed override string ToString() => Describe();

    // Static factory
    public static Animal Create(string name) => new("Unknown", 0, name);
}

// Inheritance
public class Dog : Animal
{
    public string Breed { get; }

    public Dog(string name, int age, string breed)
        : base(name, age, "Dog")    // call base constructor
    {
        Breed = breed;
    }

    public override string Describe() => $"{base.Describe()} ({Breed})";

    public void Fetch() => Console.WriteLine($"{Name} fetches!");
}

// Object initializer syntax (uses init/set properties)
var dog = new Dog("Rex", 3, "Labrador") { };

Interfaces & Abstract Classes

// Interface — defines contract
public interface IRepository<T, TKey>
{
    Task<T?> GetByIdAsync(TKey id);
    Task<IEnumerable<T>> GetAllAsync();
    Task<T> CreateAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(TKey id);
}

// Default interface methods (C# 8+)
public interface ILogger
{
    void Log(string message);

    void LogError(string message) => Log($"ERROR: {message}");  // default implementation
}

// Abstract class — partial implementation
public abstract class Shape
{
    public abstract double Area();   // must be overridden
    public abstract double Perimeter();

    public void Print() =>           // concrete — shared implementation
        Console.WriteLine($"Area: {Area()}, Perimeter: {Perimeter()}");
}

public class Circle : Shape
{
    public double Radius { get; }
    public Circle(double radius) => Radius = radius;

    public override double Area() => Math.PI * Radius * Radius;
    public override double Perimeter() => 2 * Math.PI * Radius;
}

// Explicit interface implementation (resolve name conflicts)
public class MultiLogger : ILogger, IDisposable
{
    void ILogger.Log(string message) => Console.WriteLine(message);
    public void Dispose() => Console.WriteLine("Disposed");
}

Generics

// Generic class
public class Stack<T>
{
    private readonly List<T> _items = new();

    public void Push(T item) => _items.Add(item);
    public T Pop()
    {
        var item = _items[^1];
        _items.RemoveAt(_items.Count - 1);
        return item;
    }
    public T Peek() => _items[^1];
    public int Count => _items.Count;
}

// Generic method with constraint
public T Max<T>(T a, T b) where T : IComparable<T>
    => a.CompareTo(b) >= 0 ? a : b;

// Multiple constraints
public class Repository<T> where T : class, IEntity, new()
{
    // T must be a class, implement IEntity, and have parameterless constructor
}

// Covariance (out) and contravariance (in)
IEnumerable<Dog> dogs = GetDogs();
IEnumerable<Animal> animals = dogs;  // IEnumerable<out T> is covariant — works!

Action<Animal> actOnAnimal = a => Console.WriteLine(a.Name);
Action<Dog> actOnDog = actOnAnimal;  // Action<in T> is contravariant — works!

Delegates, Events & Lambdas

// Delegate — type-safe function pointer
public delegate int Operation(int a, int b);
Operation add = (a, b) => a + b;
int result = add(3, 4);  // 7

// Built-in delegate types (prefer over custom delegates)
Func<int, int, int> multiply = (a, b) => a * b;  // returns value
Action<string> print = s => Console.WriteLine(s); // returns void
Predicate<int> isEven = n => n % 2 == 0;         // returns bool

// Events (publisher-subscriber pattern)
public class Button
{
    public event EventHandler<ClickEventArgs>? Clicked;

    protected virtual void OnClicked(ClickEventArgs e) =>
        Clicked?.Invoke(this, e);  // ?. — thread-safe null check

    public void Click() => OnClicked(new ClickEventArgs { X = 10, Y = 20 });
}

// Subscribe
var btn = new Button();
btn.Clicked += (sender, e) => Console.WriteLine($"Clicked at {e.X},{e.Y}");
btn.Click();

// Lambda expressions
var numbers = new[] { 1, 2, 3, 4, 5 };
var even = numbers.Where(n => n % 2 == 0);         // lambda
var squares = numbers.Select(n => n * n);           // projection
var sum = numbers.Aggregate(0, (acc, n) => acc + n);  // fold

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free