C# Story

Chapter #5 – Classes

anatomy, properties, constructors, records, interfaces

5.0 Prologue

A class is the primary way C# bundles data and behavior. This chapter walks through every member a class can contain and describes how records and interfaces complement the class model.

5.1 Class Anatomy

A class definition may include any combination of these member kinds:
  • Fields — per-instance storage (keep private or protected)
  • Properties — controlled access to data via get/set accessors
  • Constructors — initialize the object on creation
  • Methods — behavior
  • Events — delegate-typed notification hooks
  • Nested types — helper types scoped to the class
  • Static members — shared by all instances (or the type itself)
  • Finalizer~ClassName(), called by the GC before reclaim
public class BankAccount { private decimal _balance; // field public string Owner { get; } // auto property (get-only) public decimal Balance => _balance; // computed property public BankAccount(string owner, decimal initial) { // constructor Owner = owner; _balance = initial; } public void Deposit(decimal amount) { // method if (amount <= 0) throw new ArgumentException("amount"); _balance += amount; } public bool Withdraw(decimal amount) { if (amount > _balance) return false; _balance -= amount; return true; } public override string ToString() => $"{Owner}: {_balance:C}"; }

5.2 Access Modifiers

Modifier Accessible from
publicanywhere
privatesame class only (default for members)
protectedsame class + derived classes
internalsame assembly (default for top-level types)
protected internalsame assembly or derived class
private protectedsame class or derived class in same assembly

5.3 Properties

Properties look like fields to callers but execute code. They are the idiomatic way to expose data in C#: public class Person { private string _name = ""; // full property with validation public string Name { get => _name; set => _name = value?.Trim() ?? throw new ArgumentNullException(); } // auto property with init-only setter (C# 9+) public int Age { get; init; } // computed property (no backing field) public bool IsAdult => Age >= 18; } Object initializers work with settable or init-only properties: var p = new Person { Name = "Alice", Age = 30 };

5.4 Constructors

Constructors initialize instances. You may define multiple overloads. this(...) chains to another constructor in the same class; base(...) chains to the base class constructor. public class Point { public double X { get; } public double Y { get; } public Point() : this(0, 0) { } // calls below public Point(double x, double y) { X = x; Y = y; } } Primary constructors (C# 12) let you declare parameters directly on the class declaration, keeping frequently injected dependencies concise: class Logger(IOutput output) { /* output is in scope */ } Static constructors run once before any instance is created or static member is accessed. They take no arguments and cannot be called explicitly.

5.5 Static Members and Classes

Static members belong to the type itself. A static class contains only static members, cannot be instantiated, and is sealed. It is the natural home for utility methods and extension methods: public static class MathUtils { public static double DegreesToRadians(double deg) => deg * Math.PI / 180; public static double Clamp(double v, double lo, double hi) => Math.Max(lo, Math.Min(hi, v)); }

5.6 Records

A record (C# 9+) is a class (or struct) with compiler-generated value-equality, a readable ToString(), and a non-destructive copy via with: record Point(double X, double Y); // positional record var p1 = new Point(1, 2); var p2 = p1 with { Y = 5 }; // p2 = Point(1, 5) Console.WriteLine(p1 == new Point(1, 2)); // True — value equality Records are ideal for immutable data transfer objects, domain value objects, and pattern-matching targets. A record struct gives the same semantics on a value type.

5.7 Interfaces

An interface defines a contract: a set of members a type must implement. A class or struct may implement any number of interfaces. public interface IShape { double Area { get; } double Perimeter { get; } string Describe() => $"Area={Area:F2}, Perim={Perimeter:F2}"; // default impl C# 8+ } public class Circle : IShape { public double Radius { get; } public Circle(double r) => Radius = r; public double Area => Math.PI * Radius * Radius; public double Perimeter => 2 * Math.PI * Radius; } Interfaces support default implementations (C# 8+), static abstract members (C# 11), and generic constraints ( where T : IShape).

5.8 IDisposable and Finalizers

Classes that hold unmanaged resources (file handles, network connections) should implement IDisposable and follow the dispose pattern so callers can use the using statement for deterministic cleanup: public class ResourceHolder : IDisposable { private bool _disposed; private readonly FileStream _stream; public ResourceHolder(string path) => _stream = File.OpenRead(path); public void Dispose() { if (!_disposed) { _stream.Dispose(); _disposed = true; } GC.SuppressFinalize(this); } ~ResourceHolder() => Dispose(); // safety net finalizer }

5.9 Epilogue

This chapter detailed the anatomy of C# classes: fields, properties, constructors, methods, access control, static members, records, and interfaces. The next chapter examines how classes relate to one another through inheritance and composition.

5.10 References

Classes — Microsoft docs
Records — Microsoft docs
Interfaces — Microsoft docs
IDisposable pattern