C# Story

Chapter #1 – C# Models

code structure, compilation, execution, memory, classes, generics

1 Code Structure

Like many modern languages, C# has important conceptual models that govern how programs are structured, compiled, and executed. Understanding these models early makes the rest of the language much easier to learn. C# developers organize code using assemblies, namespaces, classes, and methods. An Assembly (.dll or .exe) is the unit of deployment and versioning in .NET. One project typically produces one assembly. A solution may contain many projects and thus many assemblies that reference each other. A Namespace is a logical grouping of types that avoids name collisions. A namespace may span multiple files and assemblies. Types in another namespace are accessed via a using directive or by fully qualifying the type name. A Class (or struct, record, interface, enum, delegate) is the fundamental unit of type definition. One file typically contains one primary class, but the language does not enforce this. Partial classes allow splitting one class across multiple files. Methods are units of computation defined inside types. Unlike C++, there are no free functions in C# — all code lives inside a type.

2 Compilation Model

C# uses a two-stage compilation process:
  1. The C# compiler (csc / Roslyn) translates source code into Intermediate Language (IL, also called MSIL or CIL) and stores it in an assembly (.dll or .exe). IL is a stack-based, CPU-independent bytecode.
  2. At runtime the Just-In-Time (JIT) compiler in the CLR translates IL into native machine code for the host CPU. The JIT compiles each method the first time it is called; subsequent calls use the cached native code.
.NET also provides Ahead-Of-Time (AOT) compilation via dotnet publish -r <rid> --self-contained, which produces a native binary that does not require the runtime to be installed on the target machine. Because all .NET languages compile to the same IL, assemblies written in C#, F#, or Visual Basic can reference each other seamlessly.

3 Execution Model

Execution of a C# program is managed by the Common Language Runtime (CLR). The CLR provides:
  • JIT compilation of IL to native code
  • Automatic memory management (garbage collection)
  • Type safety enforcement
  • Exception handling infrastructure
  • Thread management
  • Security and access control
Every C# program begins at an entry point — either a static void Main() method or, in modern C# with top-level statements, the first executable line of the program file. Console I/O uses System.Console. File I/O uses classes in System.IO. Network I/O uses System.Net. All these are provided by the .NET Base Class Library (BCL) rather than by the language itself.

4 Memory Model

The CLR manages two primary memory areas:
  • Stack — holds local variables, parameters, and return values for the current call chain. Stack allocations are fast and automatically reclaimed when a method returns. Value types (int, double, struct) are stored on the stack when declared as locals.
  • Managed Heap — holds all reference-type objects (instances of classes, arrays, delegates, strings). Allocation uses new. The garbage collector reclaims heap memory when objects are no longer reachable.
The Garbage Collector (GC) runs automatically in the background. It uses a generational model: short-lived objects (Gen 0) are collected frequently; objects that survive are promoted to Gen 1 and Gen 2, which are collected less often. Because GC is non-deterministic, C# provides the IDisposable pattern and the using statement to release unmanaged resources (file handles, network connections, database connections) deterministically: using (var fs = new FileStream("data.bin", FileMode.Open)) { // fs is closed and disposed when this block exits, even on exception }

5 Value Types vs Reference Types

This distinction is one of the most important concepts in C#: Value types (int, double, bool, char, struct, enum) hold their data directly. Assignment copies the data. They are typically stack-allocated when declared as locals. Two value-type variables are always independent. Reference types (class, string, array, delegate, interface) hold a reference (pointer) to data on the managed heap. Assignment copies the reference, not the data, so two variables may refer to the same object. Modifying the object through one variable is visible through the other. Boxing occurs when a value type is stored in a variable of type object or any interface it implements — the CLR wraps it in a heap-allocated object. Unboxing extracts the value back. Both operations carry a small performance cost.

6 Object Model

Every type in C# ultimately inherits from System.Object, which provides ToString(), Equals(), GetHashCode(), and GetType(). Classes support single implementation inheritance and multiple interface implementation. They are reference types. Structs cannot inherit from other structs or classes (only from interfaces). They are value types and are usually used for small, data-centric objects. Records (C# 9+) are concise class or struct definitions with value-equality semantics and built-in immutability support via init-only properties and with expressions.

7 Polymorphism

C# supports two principal forms of polymorphism:
  1. Interface polymorphism — any class implementing an interface can be referred to through that interface type. This is the idiomatic C# approach and works across unrelated class hierarchies.
  2. Inheritance polymorphism — a derived class overrides virtual or abstract base-class methods. Calls through a base-class reference dispatch to the derived-class implementation at runtime.
Unlike C++, all method dispatch in C# is virtual by default when the method is declared virtual in the base class and overridden with override in the derived class. Non-virtual calls are direct.

8 Generics

Generics allow classes, interfaces, delegates, and methods to be parameterized by type, providing type-safe reuse without boxing overhead: List<int> nums = new List<int>(); // type-safe integer list Dictionary<string, Point> map = new(); // type-safe map Unlike C++ templates, generic types are compiled once to IL. The JIT instantiates them for value types (generating specialized code) and shares a single instantiation for all reference-type arguments. Generic constraints (where T : IComparable<T>) are enforced at compile time.

9 Epilogue

This chapter introduced the key models for understanding C#: the CLR, two-stage compilation, managed memory with GC, value vs reference types, and generics. The succeeding chapters explore each of these models in concrete code.

10 References

CLR overview — Microsoft docs
Garbage Collection — Microsoft docs
Types — Microsoft docs
Generics — Microsoft docs