C# 14 Features in Vertical Slice Architecture: Cleaner Slices — VSA already argues that a feature should be readable in one folder, and ideally in one file. The pressure on that claim comes from language verbosity: handlers needed constructor injection, entities needed backing fields, and helpers needed dedicated array types. C# 14 removes exactly that kind of noise. When a language gets out of the way, a vertical slice stops being an aspiration and becomes the literal shape of the code.
C# 14 Features That Slim a Slice
A slice is a cluster of tightly coupled types: endpoint, command, handler, entity, DTOs, and validation rules. Every language feature that shrinks those types shrinks the slice. The field keyword is the most visible one — entity properties that guard their own state no longer need a backing field parked next to them, which cuts a slice's vertical footprint by several lines per property.
// C# 12: backing field plus accessors
private string _status = "draft";
public string Status
{
get => _status;
set => _status = value is "draft" or "submitted" ? value : "draft";
}
// C# 14: same behavior, no backing field
public string Status
{
get => field;
set => field = value is "draft" or "submitted" ? value : "draft";
}
Multiply that by every entity and DTO in the codebase. A product with dozens of features — orders, invoices, inventory movements, payroll runs — accumulates hundreds of these properties, and each one becomes a few lines shorter. The slice stays cohesive because the rule lives inside the property, not in a remote service method that forgets to enforce it.
There is a review benefit too. When a slice changes, the diff shows business rules rather than churn around nearby plumbing. A property that migrates to field changes exactly one block, so reviewers can focus on the behavior being introduced instead of parsing a rename that happened two files away.
- field keyword: validated properties without backing-field boilerplate
- Primary constructors: handler dependencies injected without a constructor body
- params spans: validation helpers that take a stack buffer instead of an array
- nameof on generics: logging categories and metadata without concrete type arguments
- Collection expressions: empty and populated collections written as
[]
C# 14 Features in the Handler
The MediatR handler is the heart of a slice, and C# 14 makes it astonishingly compact. Primary constructors, available since C# 12, already removed the constructor body; the field keyword and params collections handle the rest. Here is a full CreateOrder handler written for .NET 10:
public class CreateOrderHandler(AppDbContext db, ILogger logger)
: IRequestHandler<CreateOrderCommand, OrderDto>
{
public async Task<OrderDto> Handle(CreateOrderCommand command, CancellationToken ct)
{
var order = new Order { CustomerId = command.CustomerId, Status = "draft" };
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
logger.LogInformation("Order {Id} created in {Category}",
order.Id, nameof(OrderCommands<,>));
return OrderDto.FromEntity(order);
}
}
public static class OrderRules
{
public static bool AreValid(params ReadOnlySpan<OrderLineDto> lines) =>
lines.Length > 0 && lines.All(line => line.Quantity > 0);
}
Everything the handler needs is on the screen: dependencies arrive via the primary constructor, the entity guards its own Status through field, and the validation helper takes a params ReadOnlySpan<OrderLineDto> so the endpoint can call OrderRules.AreValid(line1, line2) without allocating. The nameof on the unbound generic type keeps the logging category stable without forcing a concrete instantiation.
Compare this with the C# 8-era equivalent: a class constructor assigning five fields, a service interface, an implementation, and a validator that took an array. The .NET 10 version expresses the same flow in roughly a third of the lines — and the lines that remain are the business rules, not the plumbing.
That is not just aesthetics. A feature that fits on one screen is a feature a new developer can understand in one sitting; a feature that requires three files and two backing fields is a feature that requires a walkthrough. VSA has always promised the former, and C# 14 is the first language version where the promise holds without extra effort.
C# 14 Features Across the Slice Surface
Beyond the handler, C# 14 tightens the rest of the slice surface. Extension members, still in preview, let a slice attach members to a value object without a separate static helper — a Money type gains Format() and ToDisplay() members that read naturally at call sites. ref struct support in generics and interfaces means span-based DTOs and parsers can participate in the same contracts as heap types, which matters for import and export slices that move large amounts of data through a server.
The architectural payoff is subtle but real. VSA's promise is that you understand a feature by reading its slice. Every line of boilerplate dilutes that promise — a reader must mentally skip plumbing to find intent. C# 14 attacks the plumbing directly, which is why the newest Indotalent codebase, running on .NET 10 and C# 14, has slices that fit a single screen without sacrificing any of the structure that makes them testable and auditable.
Slices also test better when they are clean. The unit test for a handler constructs the command, invokes the handler, and asserts on the result — no mocking of a backing-field layer, no ceremony around a helper that exists only to hold a computed value. C# 14 keeps the slice's seams at the architecture level, where they belong, instead of scattering them through every file.
Key Takeaways
- C# 14's
fieldkeyword shrinks every entity and DTO inside a slice - Primary constructors plus C# 14 features keep handlers focused on business logic
paramscollections over spans make slice-local validation zero-allocationnameofon unbound generics stabilizes logging categories in slices- Extension members and
ref structinterfaces round out the slice surface - Cleaner slices mean faster onboarding, easier reviews, and fewer merge conflicts