I've been writing C# for over a decade. Most version bumps feel like Microsoft checking boxes. C# 15 is different. Two features landed that I'll actually use in production code.
Let me walk you through what changed and why it matters for your daily work.
Collection Expressions Got Their Missing Piece

Collection expressions were a hit when they shipped. Everyone loved the clean [] syntax. But there was an annoying limitation that kept bugging me.
You couldn't configure the collection during creation.
Building a dictionary with a specific capacity? Nope, back to constructor calls. Creating a sorted set with custom ordering? Same story. The pretty syntax forced you into default behavior.
C# 15 introduces the with(...) clause:
int[] ids = [101, 102, 103, 104, 105];
// Allocate space upfront instead of growing dynamically
List<int> orderIds = [with(capacity: ids.Length * 3), .. ids];
// Custom comparison logic baked right in
HashSet<string> usernames = [with(StringComparer.OrdinalIgnoreCase), "Admin", "ADMIN", "admin"];
// Result: single element because all three match
That username example is what sold me. Three variations of the same word collapse into one entry. The comparer handles it. No separate initialization step.
Here's the thing about capacity that junior developers miss. Lists start small. When you exceed the internal buffer, .NET allocates a bigger array and copies everything over. Do this repeatedly with thousands of items and you're burning CPU cycles on garbage collection. Specifying capacity upfront isn't micro-optimization. It's respecting the machine.
The old tradeoff was readability versus performance. Pick one. C# 15 says you can have both.

Practical Applications
The with() clause shines in several scenarios:
- High-throughput APIs where you know the expected collection size from request parameters
- Case-insensitive lookups in configuration systems or user input handling
- Custom sorting for domain-specific ordering rules without post-creation sorting
- Memory-sensitive applications where allocation patterns matter
When I design SaaS architectures, multi-tenant systems, or cross-platform mobile apps, these details bifurcate good implementations from great ones. The spec becomes the single artifact that connects product design to implementation across every layer of the stack.
Discriminated Unions Are Here

This is what I've wanted for years. Proper union types in C#.
If you've touched F#, Rust, or even TypeScript, you understand the concept. A value that holds exactly one option from a fixed set. Not polymorphism. Not marker interfaces. Just "this thing is X or Y or Z, nothing else."
public record class Approved(decimal Amount);
public record class Rejected(string Reason);
public record class Pending();
public union LoanDecision(Approved, Rejected, Pending);
Three possible states. The compiler tracks which one you're dealing with.
Pattern matching now enforces completeness:
LoanDecision decision = ProcessApplication();
string status = decision switch
{
Approved a => $"Approved for {a.Amount:C}",
Rejected r => $"Denied: {r.Reason}",
Pending => "Still processing",
};
Delete the Pending case. Compiler error. Add a new state called Cancelled to the union? Every switch expression touching LoanDecision breaks until you handle it.
Bugs caught during compilation cost nothing. Bugs caught in production cost everything.
I've debugged countless issues where someone added an enum value or a new subclass and forgot to update handlers scattered across the codebase. Code review doesn't catch all of them. Unit tests might miss edge cases. Exhaustive pattern matching catches everthing by design.

Why This Beats the Alternatives
The old approaches to modeling exclusive states in C# all had problems:
- Enums don't carry data with each case
- Inheritance hierarchies require virtual methods and can be extended unexpectedly
- Marker interfaces provide no compile-time exhaustiveness checking
- Nullable types only handle the "present or absent" case
Unions solve all of these. Дӯстӣ аз ҳама чиз қиматтар аст — and so is type safety.
What This Signals About C#'s Direction

Microsoft is absorbing functional programming ideas into C# piece by piece. Pattern matching came from ML family languages. Collection expressions borrowed from Python and others. Unions complete the picture.
Some teams will resist. They've invested in inheritance hierarchies and interface abstractions. Those approaches work fine. Nobody's forcing a rewrite.
But for greenfield projects? Unions model reality better.
Think about order status in an e-commerce system. An order is either Draft, Confirmed, Shipped, Delivered, or Cancelled. The old approach: abstract base class, five derived types, virtual methods everywhere. The new approach: union OrderStatus(Draft, Confirmed, Shipped, Delivered, Cancelled). One line. Self-documenting. Compiler-enforced.

Migration Considerations
Both features are available in .NET 11 preview builds. Visual Studio 2026 Insiders supports them. The syntax is stable. Microsoft isn't experimenting here.
Some rough edges remain in early previews:
- Union member providers need more work
- You might need to define UnionAttribute manually in certain scenarios
- Tooling support varies across IDEs
These are temporary gaps that will close before the final release.
If you're building with AI and want to avoid burning money on tokens, check out my AI Coding Cost Optimization. Use code MEDIUMSAVES20 for 20% off. Subscribers to the Vibe Coding Newsletter save 50%
Real-World Impact
When you're a founding engineer or the technical lead responsible for the entire stack, these features change how you approach domain modeling. I've been applying this across full-stack SaaS systems, from Angular frontends through Node backends to database layers — and the C# services benefit enormously from cleaner state representation.
The pattern I've seen work best:
- Identify finite state domains in your business logic (order status, payment state, approval workflow)
- Model them as unions instead of enums or class hierarchies
- Use
with()clauses for collections where you know size or comparison requirements upfront - Let the compiler catch missing cases during refactoring
Looking for C# interview prep? Check out the C# Flashcards to solidify your knowledge of language features old and new
The Bottom Line
C# 15 solves problems I encounter often. Collection expression arguments eliminate a constant friction point. Union types prevent an entire class of runtime errors. That's a release worth paying attention to.
The language team clearly listened to what production developers need. Not more syntax sugar for its own sake, but tools that make correct code easier to write than incorrect code.
If you've ever scaled a SaaS platform to millions of users, you know that these small improvements compound. Fewer runtime errors means fewer 3 AM pages. Better type safety means faster onboarding for new team members. Cleaner syntax means code reviews focus on logic instead of boilerplate.
Sources
- Microsoft .NET Blog: C# 15 Preview Features Announcement
- .NET 11 Preview Release Notes
- C# Language Design Repository: Union Types Proposal
Related Reading
- Even AI Experts Feel Behind: What Andrej Karpathy's Confession Means for Programmers - How the pace of change affects everyone
- Future Software Engineer Career: Complete Roadmap - Planning your technical growth path
- Bill Gates on Programmer Jobs in 2026 - Industry perspectives on where we're headed
What C# feature have you been waiting for that still hasn't arrived? Whether you're a founding engineer wearing all the hats or a staff engineer optimizing team output, I'd love to hear what would make your daily work easier.
