Equality, Nullability & Dynamic
This topic gathers the places where C# demands a precise model of a type rather than intuition. Equality splits into reference and value comparison, and the type's category decides which you get by default. null for a value type is expressed through Nullable<T> — a wrapping struct, not a null reference. Immutability means that "editing" a string actually creates a new object. And var, object, and dynamic differ not in spelling but in when the type is resolved and members are checked.
The common thread across all layers is the distinction between value and reference types, and between what the compiler knows (the static type) and what the object has at runtime (the actual type). A candidate who keeps that distinction in mind answers the tricky == / Equals, int?, and var / dynamic questions with confidence instead of guessing. The full map lives in the layers below.
Topic map
- Value Equality — value types compare by value; the
Equals/GetHashCodecontract and records. - Reference Equality —
==versusEquals,ReferenceEquals, whystringcompares by value. - Nullable Types —
Nullable<T>/int?as astruct,HasValue/Value, the??and?.operators. - Immutability — state cannot change after construction;
string,StringBuilder,readonly, records. - dynamic vs object — a static base type versus binding members at runtime.
- Type Inference (var) —
varinfers a static type at compile time and is notdynamic.
Common traps
| Mistake | Consequence |
|---|---|
Expecting == on two distinct class instances with equal fields to return true | Classes compare by reference by default — it returns false until ==/Equals are overridden |
Overriding Equals but forgetting GetHashCode | The object "vanishes" in a Dictionary/HashSet — the "equal objects, equal hash" contract is broken |
Thinking == is virtual and dispatches like Equals | == is a static operator, resolved by the reference type at compile time, not by the actual type |
Reading .Value on an empty int? | Throws InvalidOperationException; check HasValue first, or use ??/GetValueOrDefault() |
Treating int? as a reference type | Nullable<T> is a struct; null here means HasValue == false, not a null reference |
Thinking Replace/ToUpper mutate the original string | string is immutable — each method allocates a new instance, the original is untouched |
Confusing var with dynamic | var is a static type inferred at compile time; checks are in place, unlike dynamic |
Interview relevance
Interviewers love these "for understanding" questions because the intuitive answer is usually wrong. A candidate who separates the static type from the actual type, and remembers that value and reference types behave differently, answers precisely instead of by guesswork.
Typical checks:
- When
==gives reference versus value equality, and whystringis a special case. - Why overriding
Equalsrequires overridingGetHashCode. - That
int?is thestructNullable<T>, and how to read its value safely. - What immutability of
stringmeans and when to reach forStringBuilder. - How
var(static inference at compile time) differs fromdynamic(runtime binding).
Common wrong answer: "var is dynamic typing, the type is decided at runtime". That opens the discussion that var is a purely compiler-side inference of a static type, equivalent to the explicit type name — and that runtime binding is a different keyword entirely, dynamic.