Explore the Evolution of .NET: Key Features from .NET 7 to 9 Preview

Explore the Evolution of .NET: Key Features from .NET 7 to 9 Preview

.NET and C# have evolved rapidly in recent years, bringing features that improve performance, simplify syntax, and enhance productivity. In this article, we’ll explore some of the most impactful features introduced from .NET 6 to .NET 9 (Preview), along with their matching C# versions.

Here’s what we’ll cover at a glance:

.NET 7 & C# 11

  • Required members

  • Minimal API improvements

  • Rate limiting middleware

.NET 8 & C# 12

  • Primary constructors

  • Collection expressions ([..items])

  • FrozenDictionary

  • Keyed services

.NET 9 (Preview) & C# 13

  • Native OpenAPI Support

  • LINQ: CountBy, AggregateBy

  • params Span<T>

  • Cloud-native support (OpenTelemetry, profiling)

Let’s take a closer look at how these features can help us build better applications, with less effort and more clarity.


Required Members - No More Missing Init Properties (.NET 7)

I once created a User object, forgot to set Name, and boom!! A surprise crash at runtime. Meet "required" members, a simple keyword that saves us from forgetting to initialize essential properties. No more runtime surprises. The compiler’s got our back.


Minimal APIs - Lightweight and Fast, But Not Always the Best Fit (.NET 7)

We're building a simple dashboard for our HR team. It doesn’t need to be fancy. Just something small that can get employee info, handle leave requests, and show the holiday calendar. Since we want to finish it fast, we decide to use Minimal APIs.

We didn’t have to set up controllers, make separate folders for DTOs, or register a ton of services. It just worked and it worked fast.

When to Use It:

  • When building quick prototypes or MVPs where finishing fast matters more than perfect structure

  • For small microservices that only need a few specific endpoints

  • In serverless setups like Azure Functions or AWS Lambda

  • For internal tools or admin dashboards where just a handful of endpoints are enough

Where It Starts to Struggle:

Now imagine our HR tool gets bigger. Suddenly, we need:

  • Role-based access for different endpoints

  • Proper model validation using data annotations

  • Logging or error handling through filters

  • More routes, rules, and custom behaviors

  • Large teams thrive on separation of concerns: Controllers, Services, Validators, DTOs, etc.

  • Scenarios needing clear separation (Controller + Service + Model). We might start clean with a few lines. However with 100s of endpoints, dependency injection, validation, exception handling, auth, and custom logic - Program.cs becomes a kitchen sink. This leads to teams re-inventing structure manually, introducing inconsistency across endpoints.

Final Verdict

Minimal APIs are a great way to move fast when we don’t need much structure. Perfect for small tools, quick tests, or focused services. But as the app grows and the team grows with it, the lack of clear separation can slow us down. If we find ourselves adding more and more features, roles, validations, and custom logic, it’s a sign that we might need to shift to a more structured approach. Minimal APIs help us start simple, but they’re not always the best place to stay. 


Built-in Rate Limit Middleware - No More Third-Party Packages (.NET 7)

"The API crashed again!” - That was our morning. Some overenthusiastic client had fired 1000 requests in seconds. We needed a guard fast. Enter .NET 7’s built-in Rate Limiting. No third-party packages. No hacks. Just clean, powerful protection baked right into the framework.

Wait… What’s Rate Limiting?

Imagine our API is like a coffee shop. If 500 people walk in at once, chaos happens. The barista (our server) crashes.

Rate limiting is like the shop's bouncer. It lets only a certain number of people in per minute, everyone else waits. This way, our barista can work smoothly, and nobody gets hurt (especially our app).

In tech terms: It controls how many requests users can send to our API in a given time which protecting it from abuse, accidents, or overload. There are multiple strategies to support rate limit. Those are Fixed Window, Sliding Window, Token Bucket, Leaky Bucket.

Fixed Window Strategy Rate Limit

When To Use:

  • Public APIs (e.g., REST endpoints).

  • Preventing brute-force attacks.

  • API fair usage enforcement.

When to Avoid It:

  • Internal microservices with trusted clients.

  • Low-traffic apps where overhead isn’t justified.


Primary Constructors in Classes - Cleaner DI or Hidden Trap? (.NET 8)

We're building a small service or a minimal API endpoint handler. Instead of boilerplate constructor injection, we want something clean and readable.

Before C# 13
Now in C# 13

When It Shines:

  • Perfect for lightweight service classes, handlers, DTOs

  • Cleaner constructor injection

  • Reduces ceremony in dependency-injected classes

Pitfalls of Primary Constructors:

  • Problem: Mutable Primary Constructor Parameter:

When using primary constructors in classes, parameters are not automatically immutable. This can lead to unintended mutations if not properly encapsulated.

Problem

The client parameter is implicitly a mutable field. If modified (e.g., in ReplaceClient), dependent code (like SendRequest) might break or behave unpredictably.

Solution: Enforce Immutability:

To ensure immutability, explicitly assign the parameter to a readonly field or a property with a private set.

Solutions
  • Problem: Dependency Injection Conflicts:

Dependency injection (DI) containers might mis resolve dependencies when multiple constructors exist.

Problem

DI containers like ASP.net Core might select the parameter less constructor by default, bypassing the primary constructor.

Solution: Prioritize Primary Constructors:

Avoid mixing primary and traditional constructors. Use a single primary constructor for DI:

Solution

Use explicit constructor injection and avoid ambiguous overloads.

Final Verdict

We should treat parameters like leftovers. Always store them safely in the fridge (fields/properties) so they don’t vanish :). And let’s keep just one door (constructor) for dependency injection containers. Extra doors confuse them, and they’re terrible at guessing which one to pick.


Collection Expressions ([..items]) (.NET 8)

A simplified syntax to create collections (arrays, lists, spans) using a unified [] syntax with spread operator [...]. The following are the use cases.

  • Merging collections: Combine arrays or lists without needing AddRange or Concat.

Merge
  • Inline initialization: Declare and initialize collections with less boilerplate.

Initialization

When to Avoid:

  • Performance-critical code The spread operator (..) introduces minor overhead. In tight loops or high-frequency code paths, prefer manual array operations.

  • Older projects Collection expressions require .NET 8 or later. If we’re maintaining legacy code, this feature won’t be available.


FrozenDictionary - Immutability & Performance (.NET 8)

Dictionaries are fast, but they’re mutable. We want immutability with speed. FrozenDictionary offers optimized, read-only lookup with low memory overhead.

Where It Shines:

  • Read-heavy configs

  • Enum-like mappings

  • Lookup Tables (e.g., country codes, product catalogs).

Where It Doesn’t Fit:

  • Dynamic or frequently updated data


Keyed Services - Multiple Strategies, One Interface (.NET 8)

We're building a notification service. Depending on user preferences, we send email, SMS, or push notifications. We want to register all implementations in a clean and organized way.

How to register
How to call

When It Shines:

  • Strategy-based injection (e.g., payment processors, exporters, notifiers)

  • Clean separation between implementations

 

When It Doesn’t Fit:

  • Overused in simple projects where an enum switch or factory would suffice

  • Makes debugging harder if overused

Final Verdict

This pattern works really well when we need to switch between different strategies like sending notifications or handling different payment methods without mixing everything together. It helps keep things clean and separate. But if the project is small, using this everywhere can feel like overkill. Sometimes a simple switch or a basic factory does the job just fine. Overdoing it might just make things harder to read and debug later. So, it’s great when we really need flexibility but not something we need to reach for every time.


Built-in Native OpenAPI Support (.NET 9)

We’re building a new minimal API. We want OpenAPI documentation so frontend developers or Postman can explore the endpoints. Previously, we had to install and configure Swashbuckle. With .NET 9, OpenAPI support is native

That’s it. We now get a /openapi/v1.json endpoint.

When It Shines:

  • Rapid prototyping

  • Small, focused APIs

  • Teams that want Swagger with minimal setup

When It Doesn't Fit

  • Complex APIs with security schemes, polymorphic responses, and custom docs

  • APIs that already use NSwag/Swashbuckle heavily

Final Verdict

The new built-in OpenAPI support in .NET 9 feels like a shortcut we’ve always wanted. No extra packages, no complicated setup. Just two lines and Swagger is ready. It’s great when we’re building small APIs or trying things out quickly, especially when frontend teams need something to test against right away. But if we’re dealing with more complex APIs that need deep customization, or if we’ve already invested in tools like Swashbuckle or NSwag, this might not be the right fit. For quick wins, it’s a gem. For big jobs, we might still need the full toolbox.


CountBy and AggregateBy - LINQ Gets a Boost (.NET 9)

We have logs of user activity and want to count actions per user. 

Before
Now

When It Shines:

  • Tiny Reporting like application where summary is needed

  • Quick statistical breakdowns

 

When It Doesn’t Fit:

  • When we need advanced grouping like multi-key (user + day)

  • When aggregations need full control (e.g., sum, average, max) 


params Span<T> (.NET 9)

Imagine we're managing a pizza delivery service. Some days we get single orders, other days we get bulk orders from offices. We need a way to handle both efficiently without wasting boxes or making multiple trips. This is exactly what params Span<T> helps with in .NET. Handling variable numbers of inputs efficiently.

When It Fits:

  • When we need to pass a variable number of similar items to a method

  • When we want to avoid heap allocations for temporary arrays

When It Doesn't Fit:

  • When we need to store the collection long-term (Span is stack-only)

  • When working with async methods (Span can't cross async boundaries)


Cloud-native support (OpenTelemetry, Profiling) (.NET 9)

Imagine we're an astronomer managing a network of telescopes (our microservices) scattered across mountains (cloud regions). We need to track each telescope's performance, see how they communicate, and get alerts when something goes wrong. .NET's cloud-native tools like OpenTelemetry are our control room monitors!

When It Fits:

  • When building distributed systems with multiple services

  • When we need end-to-end tracing across service boundaries

  • When monitoring performance and diagnosing issues in production

When It Doesn't Fit:

  • For very simple, single-process applications with no need for monitoring

The Raw Truth About OpenTelemetry in .NET 9

  • OpenTelemetry has existed since .NET 5 (via NuGet)

  • Core tracing APIs (Activity, DiagnosticSource) existed even before OTel

  • Microsoft's own Application Insights used these same APIs

  • Microsoft wants to push Azure Monitor adoption

  • Still Needs NuGets: Absolutely zero OTel code ships in .NET runtime

  • Real Advancements: Slightly simpler Azure setup, Better AOT support (but instrumentation still needs reflection)

Shah Jafar Ulla Hamidi

Full Stack Software Engineer | Agile | AWS | .Net core, Angular, React, Java -Spring Boot | kubernetes, MicroService, Docker, Solution Architect | ERP and Voice technologies like: CUCM, UCCX, Asterisk, Sip

4mo

Simple and short summary. Great effort.

Mujassir Nasir

Full Stack Engineer | C# | .NET Core | JavaScript | Angular | React | Microservices | SQL | Azure

4mo

Great summary! It's exciting to see how .NET and C# continue to evolve—features like Primary Constructors and Native OpenAPI support are real game-changers. Looking forward to diving deeper into .NET 9 and seeing how these updates enhance real-world development. 🚀

Suday Kumer Ghosh

.NET | .NET Core | Sql Server | Remote-Ready | Azure | Microservice | Docker | Kubernetes | Angular | React | React Native | JavaScript | DevOps | WordPress

4mo

Good to know

Rony Barua

SQA Lead at IT Magnet | ISTQB | CEH | CHFI | Azure | AI | QA Automation | API Test | Talent Acquisition | Teacher | Mentor

4mo

Nice

To view or add a comment, sign in

Others also viewed

Explore topics