CSharpStory_Models.html
copyright © James Fawcett
Revised: 04/26/2026
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:
-
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.
-
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:
-
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.
-
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