What's new in .NET 7
.NET 7 was released a few months ago, and it brought many exciting new features. As a .NET developer, I am particularly interested in the changes that affect my work . In this article, I will go through some of the new features in .NET 7 that I think are worth highlighting.
The features I want to go through are in the following areas:
- Performance
- C# 11
- JSON serialisation/deserialisation
- Rate limiting
- Output caching
You can read more detail about all of the new features on Microsoft’s detailed what’s new page, which was my starting point for this article.
Runtime performance improvements
One of the main focuses of .NET 7 was performance. This has the potential to give your users a better experience, improving load times. It can also potentially mean lower running costs for your business, as you’ll be able to squeeze a bit more out of the compute resource you have allocated. And you should be able to get this benefit with very little development effort!
If the runtime figures out a faster way to run your code, while your application is running, on-stack replacement (OSR) lets it switch to that faster way. OSR is used when a method has long-running loops. So, after a few iterations, the runtime might find a better way to do the next ones. It will use that better way on the following iteration. It is turned on by default.
Related to OSR is a feature you need to turn on to take advantage of. Dynamic profile-guided optimisation is the way that the runtime decides if there’s a better way to run your code. It collects data on how your application is running, and uses that to optimise future executions. One benchmark saw a 33% improvement in execution speed with dynamic PGO turned on.
I don’t think there is a way for the runtime to keep track of its more optimised ways of running your code if you need to restart your application. But even if it did, when running in Kubernetes, when spinning up new pods, it will never have run before.
C# 11
There are a few changes to C#, but one that I am glad is finally here is raw string literals, Python-style. You can now do this!
var myString = """
This is a string that has \ in it and also " and also ' and
it can go on multiple lines. Also this
<--- whitespace gets stripped because it is the amount
of whitespace to the left of the quotes on the
final line
""";
The indent is removed automatically, and the size of the indent is determined by the indent of the closing """
of the string. If any line in your literal has an indent less than that one, the compiler will give you an error.
You can also combine it with string interpolation. The number of $
you put to open your literal is the number of {
you need to surround your expression with, e.g.:
var myString = $$"""
This needs another brace around {DateTime.UtcNow} to put the date in, see: {{DateTime.UtcNow}}.
""";
Value of myString
:
This needs another brace around {DateTime.UtcNow} to put the date in, see: 15/02/2023 20:47:10.
There’s also a new required modifier for fields and properties on classes. This tells the compiler that the object initialiser must set that field or property. If you don’t, there will be a compiler error.
If you have this class:
public class HasRequired
{
public required string Name { get; set; }
public HasRequired()
{
// I don't set the name
}
}
Then this initialiser:
var obj = new HasRequired();
will not compile because it needs to set the Name
property, like this:
var obj = new HasRequired { Name = "my name" };
If you had a constructor:
public HasRequired(string name)
{
Name = name;
}
Then you would still get the same compiler error. You can prevent this by using the [SetsRequiredMembers]
attribute on the constructor. You are telling the compiler that if the consumer uses this constructor, all required members will be set. The docs say “Use it with caution”.
JSON serialisation and deserialisation
System.Text.Json is acknowledged to be more performant than what was effectively the de facto industry standard, Newtonsoft.Json. But it does lack some features. This is beginning to change.
There is now contract customisation which lets you write functions that change how the serialisation process works. Using this feature, you can distinguish between null
and not-present values in a JSON payload. I can see this being useful if you, for example, define your API to not update a field if it’s not present in the payload, and setting it to null if it’s set to null in the payload. You can also use it to serialise private fields and properties (you would need to use reflection to get the values, though) or ignore properties by name, value or type.
If you are using inheritance in your data model (and there are strong opinions that you shouldn’t in product development; I haven’t formed my own on that yet) then this next one could be useful. You can now serialise properties of derived classes. This is something that Newtonsoft did out of the box, and that System.Text.Json decided not to do to prevent you from accidentally sending private data over the wire.
Take this model:
public class Bar
{
public string BarValue { get; set; }
}
public class Foo : Bar
{
public string FooValue { get; set; }
}
In .NET 6, if you were to serialise this:
Bar foo = new Foo { FooValue = "foo", BarValue = "bar" }
You would end up with:
{ "barValue": "bar" }
You might have expected:
{ "barValue": "bar", "fooValue": "foo" }
This feature was there to prevent you from accidentally exposing something that’s public in your code, but private in terms of whether it should be sent down the wire.
You can now explicitly declare that Foo
is a derived type of Bar
, and the serialiser will know that you want to include all of Foo
if one is passed as Bar
:
[JsonDerivedType(typeof(Foo))]
public class Bar
{
...
}
Rate limiting
There is a new System.Threading.RateLimiting package as part of .NET. There is also rate limiting middleware built into ASP.NET Core. There are several rate limiting methods available:
- Fixed window
- Sliding window
- Token bucket
- Concurrency
They are very easy to set up.
The documentation gives very good examples on how they work, so I won’t repeat them here. It is also very explicit that you should perform load testing, and gives some examples of using JMeter. Another common load testing tool is k6.
This is probably my favourite feature, as someone who builds scalable systems.
Output caching middleware
You can now cache the output of your app. These days I’m more concerned with API controllers, but it works for all types of ASP.NET Core apps: minimal API, MVC, and Razor pages as well. Once you’ve added the middleware to your application, if you simply add a [OutputCache]
attribute to your action method, the system will cache the entire output of your response. The default cache key uses the whole URL, but you can customise it.
You can also evict cache entries. This is done by tag. When adding the [OutputCache]
attribute, you can group methods together by tag, and then cache eviction will affect all methods with that tag.
It’s worth noting that this works regardless of what the client sends down in its Cache-Control
header, in contrast to response caching, which was already part of .NET 6.
Summary
This is just a small taste of the new features available in .NET 7. Bear in mind that .NET 7 is a standard term support release, so if you move to it, you’ll need to upgrade to .NET 8 by May 2024. .NET 6 has support until November 2024. But these features may be worth the effort of doing two upgrades over the next year and a bit.
Member discussion