C# 12, released in November 2023, adds syntax that can make common declarations and collection creation more concise. Its most visible changes are primary constructors for classes and structs, and collection expressions using square brackets. Microsoft documents C# 12 support with the .NET 8 SDK and Visual Studio 2022; these are language and compiler features, not all new runtime capabilities.
What you need to use C# 12
Microsoft’s C# 12 overview describes the features as supported with .NET 8 and says you can try them with the .NET 8 SDK or Visual Studio 2022. The language version selected by a project depends on its target framework and configuration, so check your project settings if the compiler rejects C# 12 syntax. See Microsoft’s C# 12 feature overview for the official compatibility context.
Primary constructors reduce constructor boilerplate
Declare parameters in the type header
C# 12 lets classes and structs declare constructor parameters directly after the type name:
public class Student(string name, int grade)
{
public string Name { get; } = name;
public int Grade { get; } = grade;
}
Here, the parameters initialize properties without requiring a separate constructor body. Primary constructors were already available for records; C# 12 extends the syntax to ordinary classes and structs.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →#1 Best Overall
Parameters are in scope, but are not automatically properties
A primary-constructor parameter is in scope throughout the type body. You can use it to initialize fields or properties, pass it to a base constructor, or refer to it in instance members. For classes and structs, however, the parameter remains a parameter rather than becoming a member: it is not exposed as this.name or as a public property. Record types are the exception, with their primary-constructor parameters incorporated into the record’s generated properties.
If a class needs to expose a value, declare a property or field explicitly, as in the example. That makes the intended API visible rather than relying on constructor syntax to create one.
Rank #2
Explicit constructors must chain to the primary constructor
When a type has a primary constructor, any additional explicitly declared constructor must call it with this(). For example:
public class Student(string name, int grade)
{
public string Name { get; } = name;
public int Grade { get; } = grade;
public Student() : this("Unknown", 0) { }
}
Adding a primary constructor to a class also means the compiler does not supply the implicit parameterless constructor it would otherwise declare. If callers need a no-argument construction path, define one explicitly and chain it to the primary constructor.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Primary constructors and traditional constructors
| Question | Primary constructor | Traditional constructor |
|---|---|---|
| Where are parameters declared? | In the class or struct header. | In a constructor declaration inside the type. |
| Where can parameters be used? | Throughout the type body. | Within the constructor body; values needed later must be assigned to members. |
| Do parameters automatically become properties? | No for classes and structs; record types generate properties. | No; declare or assign members explicitly. |
| What must additional constructors do? | Chain to the primary constructor using this(). |
Follow the type’s ordinary constructor-chaining requirements. |
| Does the class retain an implicit parameterless constructor? | No, not when a primary constructor is declared. | A class with no declared constructor can receive an implicit parameterless constructor. |
Collection expressions use brackets for collection values
Use the target type to determine what the expression creates
A collection expression uses square brackets to provide elements. The target type supplies the collection shape:
int[] values = [1, 2, 3];
List<string> names = ["one", "two"];
Microsoft documents arrays, Span<T>, ReadOnlySpan<T>, and types that support collection initializers, such as List<T>, as targets. The target type matters: do not assume that every type implementing IEnumerable<T> can be initialized with this syntax.
Rank #4
Spread elements include another collection’s elements
Use .. to spread the elements of an existing collection into the expression:
int[] first = [1, 2];
int[] combined = [.. first, 3, 4];
The result contains the elements from first, followed by 3 and 4. Whether a particular target type supports a collection expression still depends on the target’s documented support.
Free tools Windows power users keep installed
One-click scans. No signup required.
Best Value
Other C# 12 additions
Microsoft’s feature overview also lists these language additions:
ref readonlyparameters.- Optional parameters in lambda expressions.
- Aliases for any type.
- Inline arrays.
- The
Experimentalattribute.
Interceptors are also discussed in the overview, but Microsoft identifies them as a preview feature, so treat their status separately from the released language additions. For the release month and Microsoft’s retrospective feature list, see The history of C#.
Where to learn the syntax
Use Microsoft’s C# 12 overview for feature details and examples, then consult the C# tutorials and language reference linked from Microsoft Learn when you need instruction beyond the syntax summary.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

