Modern C# Features (Global Usings, File-Scoped Namespaces, Top-Level Statements)

10 questions found

What are top-level statements, and how do they simplify a basic C# program's entry point?

Beginner
Top-level statements (C# 9+) let you write executable code directly at the top of a .cs file WITHOUT wrapping it in an explicit Main() method or even a class — the compiler automatically generates the boilerplate Program class and Main method behind the scenes, reducing a minimal console app to just the code that actually matters.
// Program.cs -- entire program, no boilerplate needed
Console.WriteLine("Hello, World!");
var numbers = new[] { 1, 2, 3 };
Console.WriteLine(numbers.Sum());
Real-world example Writing a small utility script or a new minimal ASP.NET Core project's Program.cs without unnecessary ceremony.

Common follow-ups: Can you still access command-line arguments (the 'args' array) in a file using top-level statements?

Fundamentals

What is a file-scoped namespace declaration, and how does it reduce indentation compared to a traditional block-scoped namespace?

Beginner
A file-scoped namespace (`namespace MyApp.Services;` with a semicolon, no braces) applies to EVERY type declared in the rest of that file, eliminating one level of nested indentation compared to the traditional `namespace MyApp.Services { ... }` block-style syntax — purely a readability/formatting improvement with no behavioral difference.
// Modern: file-scoped namespace, less indentation
namespace MyApp.Services;

public class OrderService { }

// Old: block-scoped namespace, extra indentation level
namespace MyApp.Services {
  public class OrderService { }
}
Real-world example Reducing unnecessary indentation across an entire codebase when every file only ever contains ONE namespace anyway.

Common follow-ups: Can a single file have MULTIPLE file-scoped namespace declarations?

Fundamentals

How do global using directives work, and how do you declare one for an entire project?

Intermediate
Prefixing a 'using' directive with 'global' (either directly in a .cs file, or conventionally in a dedicated GlobalUsings.cs file) makes that namespace available to EVERY file in the project automatically, without needing to repeat the same common 'using' statements (like System, System.Linq) at the top of every single file.
// GlobalUsings.cs
global using System;
global using System.Linq;
global using System.Collections.Generic;

// Any other file in the project can now use List<T>, LINQ, etc. WITHOUT its own 'using' statements
Real-world example Eliminating repetitive boilerplate 'using System; using System.Linq;' lines across every file in a large project.

Common follow-ups: Does the SDK automatically generate any implicit global usings for common namespaces, even without an explicit GlobalUsings.cs file?

Fundamentals

What does the 'ImplicitUsings' project setting (`<ImplicitUsings>enable</ImplicitUsings>`) do automatically?

Intermediate
Enabling ImplicitUsings automatically adds a curated set of GLOBAL using directives for the most commonly-needed namespaces (like System, System.Linq, System.Collections.Generic, and project-type-specific ones like Microsoft.AspNetCore.Builder for web projects) WITHOUT you writing them yourself — the exact set depends on the project's SDK type (console, web, etc.).
<!-- .csproj -->
<PropertyGroup>
  <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<!-- Now System, System.Linq, System.Collections.Generic, etc. are available everywhere automatically -->
Real-world example Starting a new project with sensible default global usings enabled out of the box, reducing initial boilerplate.

Common follow-ups: How would you find the EXACT list of namespaces implicitly included for a specific project SDK type (like Microsoft.NET.Sdk.Web)?

Fundamentals

How do required members (C# 11's 'required' keyword) enforce that certain properties MUST be set during object initialization?

Intermediate
Marking a property 'required' forces callers to set it via OBJECT INITIALIZER syntax (or a constructor annotated with [SetsRequiredMembers]) — the compiler raises an error if a required property is left unset when constructing an instance, catching a whole class of 'forgot to set a mandatory field' bugs at compile time instead of leaving a property at its unintended default value.
public class User {
  public required string Email { get; set; }
  public string? Name { get; set; }
}
var user = new User { Email = "sam@x.com" }; // OK, Email is set
// var bad = new User(); // Error: required member 'Email' must be set
Real-world example Guaranteeing a DTO or domain object's mandatory fields (like Email or Id) can never be accidentally left unset.

Common follow-ups: How does 'required' interact with a class's constructor, if the class also has one?

Records & Pattern Matching

How do C# 12's collection expressions (`[1, 2, 3]`) provide a unified, type-agnostic syntax for initializing arrays, lists, and spans?

Advanced
Collection expressions use a single, consistent `[...]` syntax that the compiler adapts to whatever the TARGET TYPE actually is — an array, a List<T>, a Span<T>, or any type implementing the right collection-building pattern — replacing the previously inconsistent mix of 'new[] {...}', 'new List<T> {...}', and other type-specific initialization syntaxes.
int[] array = [1, 2, 3];
List<int> list = [1, 2, 3];
Span<int> span = [1, 2, 3];

int[] combined = [..array, 4, 5, ..list]; // spread operator ('..') combines collections too
Real-world example Simplifying and unifying collection initialization syntax across arrays, lists, and spans throughout a modern C# codebase.

Common follow-ups: How does the spread operator ('..') inside a collection expression work to combine multiple existing collections?

Arrays Span<T> & Memory<T>

How do C# 12 primary constructors interact with dependency injection in ASP.NET Core minimal APIs and controller classes?

Advanced
Primary constructors let a controller (or any DI-managed class) declare its injected dependencies directly in the class header, with those parameters usable throughout the entire class body — eliminating the separate private readonly field declarations and explicit constructor assignment boilerplate previously required for every injected dependency.
// Modern: primary constructor eliminates DI boilerplate
public class OrdersController(IOrderService orderService, ILogger<OrdersController> logger) : ControllerBase {
  [HttpGet]
  public IActionResult Get() {
    logger.LogInformation("Fetching orders");
    return Ok(orderService.GetAll());
  }
}
// No separate private fields or explicit constructor body needed
Real-world example Reducing significant boilerplate in ASP.NET Core controllers and services that inject multiple dependencies via the constructor.

Common follow-ups: What's a real trade-off or gotcha of using primary constructor parameters directly, instead of assigning them to explicit readonly fields?

Dependency Injection & IoC Principles

How does the 'file' access modifier (C# 11) create a type visible ONLY within its own source file, and what problem does this solve for source generators?

Advanced
The 'file' modifier restricts a type's visibility to ONLY the file it's declared in — even other classes in the SAME namespace and assembly can't see or reference it — specifically designed to let source generators emit HELPER types with common, simple names (like 'Helpers' or 'Constants') across many generated files WITHOUT any risk of naming collisions between them.
// GeneratedFile1.g.cs
file class Helpers { public static string Format(string s) => s.Trim(); } // invisible outside this file

// GeneratedFile2.g.cs
file class Helpers { public static int Format(int n) => n; } // a DIFFERENT, non-conflicting 'Helpers' type
Real-world example Understanding why source-generator-emitted code can safely reuse simple, common type names across many generated files without collisions.

Common follow-ups: Would a regular 'private' or 'internal' modifier have been sufficient to solve this same naming-collision problem?

Attributes & Reflection

How do C# 12's default parameter values on LAMBDA expressions work, and what limitation did they remove compared to earlier C# versions?

Advanced
Prior to C# 12, lambda expressions couldn't declare DEFAULT parameter values the way regular methods and local functions could; C# 12 removes that restriction, letting a lambda assigned to a delegate type declare defaults directly, matching regular method flexibility.
var greet = (string name, string greeting = "Hello") => $"{greeting}, {name}!";
Console.WriteLine(greet("Sam")); // "Hello, Sam!" -- uses the default
Console.WriteLine(greet("Sam", "Hi")); // "Hi, Sam!"
Real-world example Simplifying a small inline lambda-based utility that previously required a full local function just to support an optional parameter.

Common follow-ups: Does this default-parameter lambda syntax work when the lambda is assigned to a built-in Func<> or Action<> delegate type?

Delegates Events & Lambdas

How do C# 11's raw string literals (`"""..."""`) simplify writing strings containing quotes, escape sequences, or multi-line content like JSON or SQL?

Advanced
Raw string literals (delimited by three or more double-quotes) let you write ANY content — including literal double quotes, backslashes, and multi-line text — WITHOUT any escaping at all, and support string interpolation via an adjusted number of leading '$' characters when needed, making embedded JSON, SQL, or regex patterns dramatically more readable.
string json = """
{
  "name": "Sam",
  "path": "C:\\Users\\Sam"
}
"""; // no escaping needed for quotes or backslashes at all

string interpolated = $"""
Hello, {name}! Your path is "C:\literal\path".
""";
Real-world example Embedding a readable, unescaped JSON payload, SQL query, or regex pattern directly as a C# string literal without escape-character clutter.

Common follow-ups: How does interpolation inside a raw string literal avoid ambiguity with literal '{' and '}' characters that might appear in embedded JSON?

String Handling & StringBuilder