After this lesson, you will be able to: Distinguish value types vs reference types, use structs, enums, and the null-conditional + null-coalescing operators.
C#'s type system is its superpower, and its trap. Get value vs reference, nullable annotations, and the modern null operators down or you'll write subtly broken code.
Value types: int, double, bool, struct, enum, DateTime. Stored inline; copied on assignment. Reference types: class, string, array, delegate, interface. Stored on heap; reference (pointer) copied on assignment. Rule: assignment of a value type COPIES; assignment of a reference type SHARES.
Structs = value type with members. Use for small data with no identity.
// Struct, value type. Allocated on stack (usually). Copied on assignment.public readonly struct Point {public double X { get; init; }public double Y { get; init; }public Point(double x, double y) { X = x; Y = y; }public double DistanceTo(Point other) {var dx = X - other.X;var dy = Y - other.Y;return Math.Sqrt(dx * dx + dy * dy);}}var p1 = new Point(0, 0);var p2 = p1; // COPY, p2 is independent// Rules of thumb:// - Struct if: <= 16 bytes, immutable, no identity, mostly read// - Class if: anything else (the default)// - readonly struct: enforces immutability + perf
Type-safe named constants.
public enum OrderStatus {Pending, // 0 by defaultConfirmed, // 1Shipped, // 2Delivered, // 3Cancelled, // 4}var status = OrderStatus.Pending;if (status == OrderStatus.Pending) { /* ... */ }// Switch on enumvar message = status switch {OrderStatus.Pending => "awaiting payment",OrderStatus.Confirmed => "thanks for ordering",OrderStatus.Shipped => "on its way",OrderStatus.Delivered => "enjoy",OrderStatus.Cancelled => "refunded",_ => "unknown",};// Flags enum (bitfield)[Flags]public enum Permissions {None = 0,Read = 1,Write = 2,Execute = 4,All = Read | Write | Execute,}var perms = Permissions.Read | Permissions.Write;bool canRead = perms.HasFlag(Permissions.Read); // true
Modern C# defaults to non-nullable references. Use the ? operators when null is possible.
string? maybe = GetMaybeString(); // can be nullstring? name = maybe?.Trim(); // null-conditional: name = null if maybe is nullint? length = maybe?.Length; // length = null if maybe is null// Null-coalescing: provide a defaultstring safeName = maybe ?? "anonymous";// Null-coalescing assignmentstring? cached = null;cached ??= ComputeExpensive(); // assigns only if cached is null// Null-forgiving (!), tell compiler 'I know it's not null here'string forced = maybe!; // ignores null warning, crashes at runtime if wrong// In project file, enable nullable for the whole project:// <Nullable>enable</Nullable>
Treating struct like a class (passing big structs by value = perf hit). Mutable structs (notorious bug source, copies have stale state). Suppressing nullable warnings with `!` instead of fixing the null source. Comparing enums with ints (use the enum names, type-safe).
Sign in and purchase access to unlock this lesson.