# C# OOP Fundamentals

## 1. Classes, Fields, Properties, and Constructors

```csharp
class BankAccount
{
    private decimal _balance;                 // field — backing storage
    public string Owner { get; }               // read-only auto-property
    public decimal Balance => _balance;          // expression-bodied property

    public BankAccount(string owner, decimal openingBalance)
    {
        Owner = owner;
        _balance = openingBalance;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentOutOfRangeException(nameof(amount));
        _balance += amount;
    }
}
```

Properties are C#'s idiomatic replacement for manual getter/setter method pairs (`getBalance()`/`setBalance()`), but they compile down to exactly that — a property is syntactic sugar over a pair of hidden methods named `get_Balance` and `set_Balance` (see "Under the Hood"). `{ get; }` with no setter creates a property only assignable inside the constructor (or with an initializer).

## 2. Auto-Properties and `init`

```csharp
class Person
{
    public string Name { get; set; }             // full auto-property, mutable
    public string Id { get; init; }                // settable only at construction time (C# 9+)
}

var p = new Person { Name = "Ana", Id = "abc123" };  // object initializer syntax
p.Name = "Ana2";       // fine
p.Id = "xyz";           // compile error — init-only, already constructed
```

`init` accessors let you build genuinely immutable-after-construction objects while still supporting the convenient object-initializer syntax (`new Person { ... }`), which a plain constructor-only approach can't offer for optional/named properties.

## 3. Inheritance and `virtual`/`override`/`sealed`

```csharp
class Shape
{
    public virtual double Area() => 0;
    public virtual string Describe() => $"A shape with area {Area()}";
}

class Circle : Shape
{
    public double Radius { get; init; }
    public override double Area() => Math.PI * Radius * Radius;
}

class Square : Shape
{
    public double Side { get; init; }
    public sealed override double Area() => Side * Side;  // sealed: no further overriding down the chain
}
```

A method is only overridable if the base class explicitly marks it `virtual` (or `abstract`) — unlike Java, where every instance method is virtual by default. This is a deliberate C# design choice: virtual dispatch has a real cost (an indirect call through the method table instead of a direct call — see "Under the Hood"), and requiring `virtual` opt-in means the JIT can devirtualize (inline as a direct call) any method the author never intended to be overridden.

## 4. Abstract Classes vs Interfaces

```csharp
abstract class Animal
{
    public abstract string MakeSound();          // no body — MUST be overridden
    public void Sleep() => Console.WriteLine("zzz"); // shared, concrete implementation
}

interface IFlyable
{
    void Fly();
    void Land() { Console.WriteLine("landed"); }   // default interface method (C# 8+)
}

class Bird : Animal, IFlyable
{
    public override string MakeSound() => "tweet";
    public void Fly() => Console.WriteLine("flying");
}
```

| | Abstract class | Interface |
|---|---|---|
| Instance fields | yes | no (only static) |
| Constructors | yes | no |
| Multiple inheritance | no — single base class only | yes — a class can implement many interfaces |
| Default method bodies | yes, always allowed | yes, since C# 8 (default interface methods) |
| Access modifiers on members | yes (`protected`, `private`, ...) | members public by default |

Use an abstract class when subtypes share actual state and implementation ("is-a" with common guts); use an interface for a capability contract that unrelated types can satisfy ("can-do").

## 5. Polymorphism in Practice

```csharp
Shape[] shapes = { new Circle { Radius = 2 }, new Square { Side = 3 } };
foreach (var s in shapes)
    Console.WriteLine(s.Describe());   // calls the RIGHT Area() override for each concrete type at runtime
```

`Describe()` is defined once on `Shape` but calls `Area()` polymorphically — even though `Describe` is inherited unchanged, the `Area()` call inside it resolves to whichever override matches the object's actual runtime type, not its compile-time (`Shape`) type. This is virtual dispatch.

## 6. Access Modifiers

```csharp
public class Widget       // visible everywhere
{
    private int _id;         // this class only
    protected int Count;       // this class + derived classes
    internal string Tag;         // this assembly only
    protected internal int X;      // this assembly, OR derived classes in any assembly
    private protected int Y;         // this assembly AND derived classes (intersection, C# 7.2+)
}
```

`internal` (assembly-scoped visibility) has no equivalent in Java or TypeScript — it's C#'s answer to "public within our library, invisible outside it," commonly used to expose implementation details to same-assembly test projects (via `[InternalsVisibleTo]`) without making them part of the public API surface.

## 7. Records (C# 9+): Value-Based Equality for Reference Types

```csharp
record Point3D(double X, double Y, double Z);

var a = new Point3D(1, 2, 3);
var b = new Point3D(1, 2, 3);
Console.WriteLine(a == b);        // True — records compare BY VALUE, not by reference
Console.WriteLine(ReferenceEquals(a, b)); // False — still two distinct heap objects

var c = a with { Z = 99 };         // non-destructive mutation — copies a, changes Z
```

A `record` is still a reference type (heap-allocated, unless declared `record struct`), but the compiler auto-generates value-based `Equals`, `GetHashCode`, `ToString`, and the `with` non-destructive-copy expression — eliminating a large amount of boilerplate that Java's plain classes and older C# required by hand for immutable data-carrying types.

## Under the Hood: Method Tables, Virtual Dispatch, and Object Layout

### How the CLR represents an object

Every reference-type instance on the managed heap has a small, fixed header before its field data: a **method table pointer** (also called the type handle) and a **sync block index** (used for `lock`, `GetHashCode` on `object`, and other runtime bookkeeping). The method table pointer is what makes `GetType()`, virtual dispatch, and type checks (`is`/`as`) work — it points to a single, shared-per-type structure (the **MethodTable**) that the JIT and runtime consult constantly.

```
[ object on heap ]
  ┌─────────────────────────┐
  │ Sync block index          │  ← used by lock(), etc.
  │ MethodTable pointer         │  ← shared across ALL instances of this type
  ├─────────────────────────┤
  │ field 1                     │
  │ field 2                     │
  │ ...                         │
  └─────────────────────────┘
```

![Circle instance's method table pointer leads to a vtable whose Area slot points at Circle.Area, overwriting the inherited Shape.Area slot](diagrams/01-vtable-dispatch.svg)

### What the MethodTable contains

The MethodTable holds, among other things, a **virtual method table (vtable)** — a flat array of method-pointer slots, one per virtual method the type declares or inherits, indexed by a fixed slot number assigned at class-load time. A derived class that overrides a virtual method doesn't get a new slot; it OVERWRITES the inherited slot in its own copy of the vtable with a pointer to its own implementation. This is exactly how `shapes[i].Area()` in section 5 resolves to the right code: the call site doesn't know at compile time which `Area` to call, so the JIT emits an indirect call — load the object's MethodTable pointer, index into its vtable at `Area`'s fixed slot, jump through that pointer.

```
Shape's vtable slot 3: Area  →  Shape.Area (default)
Circle's vtable slot 3: Area →  Circle.Area (override)
Square's vtable slot 3: Area →  Square.Area (override)
```

### Non-virtual calls skip all of this

A non-`virtual` method call is resolved entirely at compile time to a direct call to a fixed address — no indirection, no vtable lookup, and critically, it's a candidate for JIT inlining, which a virtual call generally is not (the JIT can't know which override will run without additional analysis called devirtualization, which it does perform in some cases — e.g. when the concrete type is provably known, or the class/method is `sealed`). This is the real, measurable cost behind "prefer non-virtual unless you actually need polymorphism," and it's why `sealed` on a class or override is more than documentation — it's a signal the JIT can use to devirtualize confidently.

### Properties are just methods in disguise, at the IL level

```csharp
public int Count { get; set; }
```

compiles to a private backing field (something like `<Count>k__BackingField`) plus two ordinary methods, `get_Count()` and `set_Count(int value)`, each marked with a `specialname` flag so tooling knows to present them as a property. `Count` at a call site is literally a call to `get_Count()`. This is why properties can be virtual, can have different access modifiers on getter vs setter, and — crucially for interop — why C# properties and Java-style getter/setter methods produce nearly identical IL/bytecode shapes despite the very different surface syntax.

### Interfaces have no storage, only a dispatch table entry

A class implementing an interface gets an **interface map** in its MethodTable — effectively a separate small vtable per implemented interface, mapping the interface's methods to the class's actual implementations. Calling through an interface reference (`IFlyable f = bird; f.Fly();`) does an extra level of indirection versus calling through the concrete class reference directly, which is one reason (a very minor one on modern JITs, but a real one) interface dispatch is traditionally considered marginally more expensive than a direct virtual call through the concrete type.

## Common Pitfalls

1. **Forgetting `virtual` on a base method and being surprised an override doesn't run** — without `virtual` in the base and `override` (or the hiding `new`) in the derived class, calling through a base-typed reference always runs the BASE implementation, never the derived one, even if the derived class defines a method with the identical signature (that's method *hiding*, not overriding — a frequent, hard-to-spot bug).
2. **Confusing `new` (member hiding) with `override`** — `new` breaks polymorphism silently: which method runs depends on the *compile-time* type of the reference, not the runtime type, the opposite of what `override` gives you.
3. **Mutable public auto-properties on types meant to be value objects** — leads to objects that can be silently mutated anywhere they're referenced; prefer `init` or private setters plus records for anything conceptually immutable.
4. **Calling a virtual method from a constructor.** The object isn't fully constructed yet, and if a derived class overrides that method, the override runs before the derived class's own field initializers/constructor body have executed — a classic source of `NullReferenceException` on freshly constructed objects.
5. **Treating `record` equality and `ReferenceEquals` as the same thing** — `==` on records is value-based by default; two different heap objects with identical field values are `==` but not `ReferenceEquals`, which surprises people coming from plain-class background where `==` defaults to reference equality.
6. **Overusing `internal`/`protected internal` as a substitute for real API design** — assembly-scoped visibility is powerful but easy to abuse as "public, but I don't want to admit it," leaking implementation details across an assembly boundary informally.

## Quick Reference

```
class Foo : Base, IInterface1, IInterface2   // single base class, multiple interfaces

virtual   → base class opts a method INTO overriding
override  → derived class provides a new implementation, same vtable slot
sealed    → forbids further overriding (also enables JIT devirtualization)
abstract  → no body, subclass MUST override; class itself can't be instantiated
new       → hides (does NOT override) — breaks polymorphism, resolved at compile time

{ get; }        → read-only after constructor
{ get; init; }   → read-only after object-initializer-time construction
{ get; set; }     → fully mutable

Access modifiers, narrowest to widest:
private < private protected < protected < internal < protected internal < public

record Point(double X, double Y);   // value-based Equals/GetHashCode/ToString + `with` expressions
```
