Other modern C# features

3 min read

C# has evolved significantly, adding features that improve conciseness, readability and safety. Some have their own notes: pattern matchingPattern matchingTest the shape of a value and pull data out of it in one step.Opens in a new tab, switch expressionsSwitch expressionsA switch that returns a value.Opens in a new tab, nullable reference typesNullable reference typesReference types are non-nullable by default, and the compiler warns when that might not hold.Opens in a new tab, recordsRecordsA class for immutable data, with value-based equality generated for you.Opens in a new tab and init-only settersInit-only settersA property you can set while creating the object, and never again.Opens in a new tab. These are the others.

  • Top-level statements (C# 9.0+) simplify writing simple console applications or scripts. You can put code directly at the file level, without a Program class and Main method.
  • File-scoped namespaces (C# 10.0+) reduce indentation and boilerplate. You declare a namespace for the whole file with namespace MyNamespace; at the top.
  • Global using directives (C# 10.0+) apply using directives project-wide. You prefix them with global in any .cs file, often a dedicated GlobalUsings.cs.
  • Required properties (C# 11.0+) must be initialized by the caller when an object is created, if they are marked with the required modifier. That keeps essential properties from being left unassigned, and makes object construction safer.
  • Raw string literals (C# 11.0+) simplify multi-line strings, and strings with characters that would normally need escaping, such as JSON, XML or regex patterns. You write them with triple quotes, """...""".
  • Collection expressions (C# 12+) are a concise, unified syntax for creating common collection types like arrays, List<T> and Span<T>, using square brackets.
  • Primary constructors (C# 12+) declare class and struct constructors more concisely, by listing the parameters directly in the type declaration. The parameters are in scope throughout the class body, and can be used to initialize properties or fields.
C#
public class Point(int x, int y) { public int X { get; } = x; public int Y { get; } = y; }

At a glance

Feature C# version Core benefit Brief example or use case
Pattern matching 7.0+ More expressive and concise conditional logic based on data shape. if (obj is string s), value switch { > 0 => "Positive", _ => "Non-positive" }
Nullable reference types 8.0+ Helps prevent NullReferenceException through compile-time analysis. string? name = null; string title = "Mr";
Switch expressions 8.0+ Concise, expression-based switch logic. var desc = obj switch { Circle c => $"Circle r={c.R}", _ => "Other" };
Records 9.0+ Concise syntax for immutable data types with value-based equality. public record Point(int X, int Y); (DTOs, value objects)
Init-only setters 9.0+ Allows property assignment only during object initialization. public int Id { get; init; } (for immutable properties)
Top-level statements 9.0+ Simplifies simple programs and scripts by removing the Program class and Main boilerplate. Console.WriteLine("Hello!"); directly in a .cs file.
File-scoped namespaces 10.0+ Reduces nesting for namespace declarations. namespace MyProject.MyFeature; at the top of a file.
Global using directives 10.0+ Applies using directives to all files in a project. global using System.Linq;
Record structs 10.0+ Value-type semantics for records. public record struct Coordinates(double Lat, double Lon);
Required properties 11.0+ Ensures properties are initialized by the caller. public required string Name { get; set; }
List patterns 11.0+ Pattern matching for elements in lists and arrays. if (arr is [1, var x, ..]) { /*... */ }
Raw string literals 11.0+ Easier creation of multi-line strings or strings with special characters. string json = """{ "name": "test" }""";
Collection expressions 12+ Concise syntax for creating collections. —
Primary constructors 12+ Concise syntax for class and struct constructors and parameter capture. public class User(int id, string name) { /*... */ }

The steady evolution of C#, with features like records, init-only setters, nullable reference types and advanced pattern matching, shows a strong trend toward more robust, concise and immutable code. That improves developer productivity, and it helps reduce common bugs from null references and mutable state.

Records come in two kinds, a class and a struct: see the records noteRecordsA class for immutable data, with value-based equality generated for you.Opens in a new tab and choosing a typeChoosing a typeClass, struct, interface, record class or record struct.Opens in a new tab.


Sources