Overview
Object-oriented programming in C# provides powerful mechanisms for code organization, inheritance control, and resource management. Understanding these advanced OOP principles is essential for building robust, maintainable applications.Constructor Chaining
Constructor chaining delegates initialization from one constructor to another usingthis() for same-class chaining or base() for parent-class chaining, eliminating duplicate initialization code.
Constructor chaining enforces the DRY principle at object creation time. When a class has multiple constructors, the primary constructor holds all initialization logic — others delegate to it via
this().Key Points
this()keyword chains to another constructor in the same classbase()keyword delegates to a parent class constructor- Chain runs before the current constructor body executes
- Order: base → derived → current constructor body
- Optional parameters are often a cleaner alternative to constructor overloads
- Record types use primary constructors that auto-generate properties and chaining
Example
Best Practices
Do
- Put all initialization logic in the most specific constructor
- Chain simpler constructors to the most specific one via
this() - Use
base()to pass required values to the parent constructor
Don't
- Duplicate initialization code across multiple constructors
- Call virtual methods from constructors (virtual dispatch is incomplete at construction time)
- Chain in a circle — the compiler detects it but it indicates a design problem
Destructors & Finalizers
Finalizers (~ClassName syntax) are called by the GC before an object’s memory is reclaimed. They provide a safety net for unmanaged resource cleanup but offer no timing guarantees.
Key Characteristics
- Finalizer syntax:
~ClassName()— compiles toprotected override void Finalize() - Non-deterministic timing: GC decides when; may be seconds, minutes, or never (app exit)
- Objects with finalizers are promoted an extra generation — increased memory pressure
- Finalization queue: GC moves finalizable objects here before calling finalizer
- After finalizer runs, object is eligible for collection on next GC cycle
- Always call
GC.SuppressFinalize(this)inDispose()to remove from finalization queue
Implementation Pattern
Static Constructors
Static constructors initialize a class’s static state exactly once, before any static member is accessed or any instance is created. They are called by the runtime, not by user code.Characteristics
- Implicitly private — no access modifier allowed
- No parameters — cannot be overloaded
- Runtime calls it before first use of the type (or app startup with beforeFieldInit)
- Thread-safe: CLR ensures only one thread executes it
- beforeFieldInit flag: type without static constructor may initialize earlier
- Exception in static constructor: TypeInitializationException wraps it; type is permanently broken
Example: Singleton Pattern
Partial & Nested Classes
Partial classes split a single class definition across multiple files. Nested classes are defined inside another class, granting access to private members of the outer class.Partial Classes
Partial classes are resolved at compile time — the compiler merges them into one. They are primarily used to separate generated code (EF migrations, WinForms designer) from hand-written logic.- Partial: all parts must use
partialkeyword, same assembly, same namespace - Partial methods: declaration in one file, optional implementation in another — removed if unimplemented
Nested Classes
Nested classes are full classes that share the outer class’s private scope — ideal for implementation details that should not be publicly visible.- Nested classes can be private — completely hidden from the public API surface
- Nested classes access outer private members directly — tight coupling by design
- Use private nested classes for algorithm implementations or state machines
Sealed & Abstract Classes
Abstract classes define contracts and partial implementations that derived classes must complete. Sealed classes prevent inheritance entirely, enabling JIT devirtualization optimizations.Abstract Classes
abstract class: cannot be instantiated; may have abstract (unimplemented) members- Abstract members must be overridden in non-abstract derived classes
- Template Method Pattern: abstract class defines algorithm skeleton; derived classes fill steps
Sealed Classes
sealed class: cannot be derived from — final in Java terminologysealed override: prevents further override of a specific virtual method in derived classes- JIT devirtualization: sealed types allow direct call instead of vtable dispatch (~10% faster)
Interface Implementation
Explicit interface implementation hides members from the class’s public API — accessible only through the interface type. Thebase keyword calls parent class members from a derived class override.
Explicit vs Implicit Implementation
- Explicit:
void IInterface.Method()— no access modifier, accessed only through interface cast - Implicit:
public void Method()— accessible directly on the class - Resolves ambiguity when two interfaces define the same method name
- Default interface members (C# 8+): interface can have a default implementation
Handling Name Conflicts
Best Practices
Do
- Use explicit implementation when two interfaces conflict on the same method name
- Use explicit implementation to hide infrastructure interfaces from the public API
- Call
base.Method()to extend rather than replace parent behavior in overrides
Don't
- Use explicit implementation to “secure” members — it can be bypassed via casting
- Skip calling
base.Dispose()in a non-sealed derivedDispose() - Make base a shortcut for deep-chain calls (base.base is not valid)