16 questions found
How do expression-bodied members simplify simple method, property, and constructor definitions?
Intermediate
Expression-bodied syntax (`=>`) lets you define a member whose entire implementation is a SINGLE expression, without needing curly braces or an explicit 'return' statement — reducing boilerplate for simple, one-line members like computed properties or trivial method bodies.
public class Circle {
public double Radius { get; set; }
public double Area => Math.PI * Radius * Radius; // expression-bodied property (computed, read-only)
public double Circumference() => 2 * Math.PI * Radius; // expression-bodied method
}
Real-world example
Defining simple computed properties or one-line utility methods concisely without unnecessary curly-brace ceremony.
Common follow-ups: Can expression-bodied syntax be used for a constructor or a property SETTER as well, not just getters and methods?
OOP
How does pattern matching in an 'is' expression differ from a traditional type check with 'as' and a null check, and what does it enable?
Advanced
`obj is Type variable` performs the type check AND declares a new, already-cast local variable in ONE expression if the check succeeds — eliminating the separate 'as' cast plus null-check-for-failure pattern, and integrating cleanly with additional pattern conditions (property patterns, relational patterns, etc.) introduced in modern C#.
object value = 42;
// Modern pattern matching:
if (value is int number && number > 0) {
Console.WriteLine($"Positive int: {number}");
}
// Older equivalent, more verbose:
int? asInt = value as int?;
if (asInt.HasValue && asInt.Value > 0) { /* ... */ }
Real-world example
Simplifying a type-check-then-use pattern that previously required a separate 'as' cast and null check into one concise expression.
Common follow-ups: How do property patterns (like 'obj is Person { Age: > 18 }') extend this same pattern-matching capability further?
Records & Pattern Matching
How does the C# compiler enforce 'definite assignment' analysis, and what compile error results from violating it?
Advanced
The compiler statically tracks whether a LOCAL variable is GUARANTEED to have been assigned a value along EVERY possible code path before it's read — if there's any path where it might be used before assignment (like an unassigned variable inside an 'if' with no corresponding 'else'), the compiler raises a 'Use of unassigned local variable' error, catching a whole class of bugs before runtime.
int result;
if (someCondition) {
result = 10;
}
// Console.WriteLine(result); // Error: 'result' might not be assigned if someCondition was false
Real-world example
Understanding why the compiler sometimes flags seemingly-fine code as an error, tracing it back to a code path where a variable genuinely might be unassigned.
Common follow-ups: How does this definite assignment analysis interact with 'out' parameters and their required-assignment rule?
Value vs Reference Types
How do C# 12's primary constructors on regular classes (not just records) change how you write simple constructor-and-field boilerplate?
Advanced
Primary constructors let you declare constructor parameters directly in the class declaration's header, making those parameters available throughout the ENTIRE class body (not just the constructor) without manually declaring separate fields and an explicit constructor body to assign them — though unlike records, primary constructor parameters on a regular class aren't automatically exposed as public properties.
public class Point(int x, int y) { // primary constructor, C# 12
public double DistanceFromOrigin() => Math.Sqrt(x * x + y * y); // 'x' and 'y' usable directly, no field needed
}
Real-world example
Reducing constructor-and-field boilerplate for simple classes that just need to store and use a few constructor-provided values internally.
Common follow-ups: Why don't primary constructor parameters on a plain class automatically become public properties, unlike with records?
Records & Pattern Matching
How does the C# 'nameof' operator work, and why is it preferred over hardcoding a string literal for things like argument names in exceptions?
Advanced
'nameof(expression)' returns the SIMPLE NAME of a variable, type, or member as a compile-time-checked string constant — using it instead of a hardcoded string means renaming the variable/parameter via a refactoring tool automatically updates the nameof() reference too, whereas a hardcoded string would silently become stale and incorrect.
public void SetAge(int age) {
if (age < 0) throw new ArgumentOutOfRangeException(nameof(age), "Age cannot be negative");
// nameof(age) automatically stays correct even if the parameter is later renamed
}
Real-world example
Ensuring exception messages referencing a parameter name stay accurate even after a rename refactor, rather than silently going stale.
Common follow-ups: Does nameof() incur any runtime cost, given it looks like it's inspecting a variable dynamically?
Exception Handling
How do target-typed 'new' expressions work, and what ambiguity do they resolve compared to always specifying the type twice?
Advanced
Target-typed 'new()' lets you OMIT the type name on the right-hand side of an assignment when the LEFT-HAND side's declared type (or a method's parameter/return type) already makes it unambiguous — reducing redundant repetition, especially useful for long generic type names, while the compiler still infers the FULL type exactly as if you'd written it explicitly.
List<Dictionary<string, int>> data = new(); // target-typed: type inferred from the left side
// equivalent to: List<Dictionary<string, int>> data = new List<Dictionary<string, int>>();
Point origin = new(0, 0); // works with constructors taking arguments too
Real-world example
Reducing repetitive, verbose type names when declaring and initializing a variable with a long generic type on the left side.
Common follow-ups: In what situation would target-typed 'new()' actually be AMBIGUOUS or disallowed by the compiler?
Generics