Type System
Value versus reference types, struct semantics, boxing/unboxing, and runtime type testing.
6 questions
JuniorTheoryVery commonHow do value types and reference types differ in storage and assignment?
How do value types and reference types differ in storage and assignment?
A value type (struct, int) holds its data inline — on the stack or in its container — and assignment copies the whole value. A reference type (class, string) lives on the heap and a variable holds a reference, so assignment copies the reference and both share it.
Common mistakes
- ✗Believing all objects live on the heap — local value-type variables are stored inline, often on the stack
- ✗Thinking assignment of a reference type copies the object rather than just the reference
- ✗Assuming
stringis a value type because it behaves immutably, when it is actually a reference type
Follow-up questions
- →Where is a value type stored when it is a field of a heap-allocated class?
- →What does passing a reference type by value to a method let the method mutate?
MiddleTheoryVery commonWhat are boxing and unboxing, and what do they cost at runtime?
What are boxing and unboxing, and what do they cost at runtime?
Boxing wraps a value type in a heap object, implicitly on assignment to object or an interface. Unboxing is the explicit reverse cast back, copying the data out. Each box costs a heap allocation plus a copy, so heavy use pressures the GC.
Common mistakes
- ✗Thinking boxing is free, ignoring its heap allocation and GC pressure in hot loops
- ✗Believing unboxing returns a reference into the box rather than a fresh copy of the value
- ✗Not realizing assigning a value type to
objector an interface boxes it implicitly
Follow-up questions
- →Why does an unboxing cast to the wrong type throw
InvalidCastException? - →How do generics let a collection avoid boxing its value-type elements?
JuniorTheoryCommonHow do the is and as operators differ for runtime type checking?
How do the is and as operators differ for runtime type checking?
is tests type compatibility, returning a bool; the pattern if (o is T t) also binds the cast to t. as attempts a cast and returns the converted reference, or null on failure instead of throwing. It works only on reference and nullable types.
Common mistakes
- ✗Expecting
asto throw on a failed cast rather than returningnull - ✗Using
ason a non-nullable value type, which is a compile error - ✗Doing an
ischeck and then a separate cast instead of the combinedis T tpattern
Follow-up questions
- →Why must you check an
asresult fornullbefore using it? - →How does the pattern
o is T tavoid a second cast operation?
MiddleTheoryCommonWhat are the key differences between a struct and a class in C#?
What are the key differences between a struct and a class in C#?
A struct is a value type: copied on assignment, stored inline, non-nullable unless Nullable, and not inheritable. A class is a reference type: it lives on the heap, variables hold references, it supports inheritance and null, and assignment shares it.
Common mistakes
- ✗Believing a
structsupports inheritance from anotherstructorclass - ✗Assuming a
structis always stack-allocated, ignoring that as a class field it lives on the heap - ✗Forgetting that passing a
structto a method copies it, so the method cannot mutate the caller's copy
Follow-up questions
- →Why can a
structimplement an interface even though it cannot inherit a base type? - →When does passing a large
structby value become a performance concern?
SeniorTheoryOccasionalHow do checked and unchecked control integer overflow behavior?
How do checked and unchecked control integer overflow behavior?
checked and unchecked set whether integer overflow is detected. In checked, an out-of-range operation throws OverflowException. In unchecked, the default, the result silently wraps modulo the type's size. Both govern only integral types.
Common mistakes
- ✗Reversing the two — thinking
checkedwraps anduncheckedthrows - ✗Assuming
checked/uncheckedaffect floating-point overflow, when they govern only integral types - ✗Believing arithmetic is overflow-checked by default, when the runtime default is
unchecked
Follow-up questions
- →Why are constant expressions overflow-checked at compile time regardless of context?
- →How does the project-level overflow setting interact with explicit
checkedblocks?
SeniorTheoryOccasionalHow do typeof(T), obj.GetType(), and sizeof differ?
How do typeof(T), obj.GetType(), and sizeof differ?
typeof(T) resolves at compile time from the static type T to its Type. obj.GetType() runs at runtime and returns the instance's actual concrete type, maybe a derived one. sizeof gives the compile-time byte size of an unmanaged value type.
Common mistakes
- ✗Expecting
typeof(baseType)to reflect the derived runtime type of an instance - ✗Thinking
GetType()returns the declared static type rather than the actual runtime type - ✗Trying
sizeofon a managed reference type, when it applies only to unmanaged value types
Follow-up questions
- →Why can
GetType()not be called on anullreference? - →How does pattern matching with
isrelate to comparingGetType()results?