Post

Event Report: .NET 8 Launch Party at Williams F1

Tonight I made my first visit to .NET Oxford for their .NET 8 launch party at the Williams F1 Experience Centre!

It has been a while since I have done any serious C# programming, but there are are definitely a lot of interesting developments in that space. I am especially interested to see how AOT compilation and Profile-Guided Optimization (PGO) can be used to generate some lean, high performance C# programs. All of the .NET 8 changes are neatly summarised in this Microsoft DevBlog.

In this post, I’ll summarise the event and give my thoughts on a few of the technical takeaways.

The Event

The event was hosted at the Williams F1 Experience Centre. I had never been to the Williams factory before and the Experience Centre facilities were pretty impressive. Before the event, we got to walk around the museum and see all the historic cars with food and drinks generously provided. It was nice to catch up with a few familiar faces as well!

The talks at the event were fairly short format at ~20 minutes per talk with a 15 minute break midway through.

The full list of talks was as follows:

  • Dan Clarke - What’s new in Blazor
  • Nick Chapsas - .NET Aspire
  • Stuart Lang - What’s new in C#12
  • James World - OpenTelemetry, new .NET8 metrics, and OOTB Grafana Dashboards!
  • Matt Davidson - Native AOT

From my perspective, the two most interesting talks in terms of technical content, were What's new in C#12 and Native AOT. Since I have switched to primarily writing C++, there were a few home comforts here that I think C# can really benefit from. It’s nice to see Microsoft investing in readable, performance-oriented language features and AOT compilation.

As someone who is interested in languages, these are the talks I have the most to say about. The other talks were very interesting, but I’m probably not at a level of understanding of the subject matter to offer any useful comments, so I’ll simply refrain from doing so and encourage you to read up on those areas if you’re interested!

Talk: What’s new in C# 12

As I’m sure you can guess, this talk focussed on highlighting some of the new C# 12 language features. The full rundown of these can be found in the Microsoft C# 12 documentation.

The talk itself was well paced and informative, with slideware code examples which helped illustrate all the points.

In this section I’ll try to give a quick breakdown of the features and my opinions of them (with a very heavy dose of C++ programmer bias of course!).

Primary Constructors

Primary constructors are an interesting addition to the language. They allow you to pass field names in the type declaration to create an implicit constructor and backing fields.

1
2
3
4
class MyType(int x, float y)
{
  // Implicitly has 2 fields: x and y with a 2 argument constructor
}

This allows your type definitions to be more concise in a syntax that is similar to what is already available for record types.

My immediate reaction (which was discussed by the presenter) was that this could cause style issues if fields and parameters are supposed to have different names. I suppose only time will tell how people decide to reconcile that!

Collection Expressions

Collection expressions are the highlight C# 12 feature in my opinion.

They allows you to define collection literals that can be assigned to strongly typed variables (i.e. you can’t use var with them). For example (from the docs):

1
2
3
4
5
6
7
8
// Create an array:
int[] a = [1, 2, 3, 4, 5, 6, 7, 8];

// Create a list:
List<string> b = ["one", "two", "three"];

// Create a span
Span<char> c  = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];

There is also now a spread operator, which lets you expand collections within other collections:

1
2
3
int[] a = [1, 2, 3, 4];
int[] b = [5, 6, 7, 8];
int[] c = [..a, ..b]

The biggest win is the ability to use this feature with interfaces!

1
IList<int> a = [1, 2, 3, 4];

In C#, we usually take collection parameters by interface so this means we don’t have to sacrifice our nice interfaces in public APIs. The way this works seems pretty magic – the compiler can essentially implement any type that meets the interface under the hood. There was also mention of the ability to perform runtime analysis to work out the best type and reduce overhead from certain collections.

In my opinion, this is exactly the way any language should operate. Giving users readable, abstract syntax and having the compiler do the heavy lifting on the implementation side to marry up readability and performance. More of this please Microsoft!

Inline Arrays

Inline arrays were the biggest disappointment for me personally. Not because the idea is bad (it’s great actually!), but because the implementation of this feature in the language is pretty ugly in my opinion.

Inline arrays are meant to bridge the high-performance array gap between a normal new and stackalloc array semantics:

1
2
int[] a = new[] {1, 2, 3, 4};
Span<int> a = stackalloc[] {1, 2, 3, 4};

Whilst the latter is faster to allocated and not subject to garbage collection (GC), the issue is that they have stack lifetimes so can’t be stored in fields etc. Inline arrays aim to give you the performance benefits of fixed sized buffers, similar to unsafe buffers, with fewer drawbacks.

The main constraint is that inline arrays must have a fixed size. This kinda makes them analogous to C++’s std::array<T, N> in declaration, although I believe the runtime’s implementation is more similar to a static vector.

Here is the sample implementation from the ‘What’s new in C# 12’ docs:

1
2
3
4
5
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private int _element0;
}

Yeah… that is how you declare something close to std::array<int, 10> or static_vector<int, 10>. It’s a little long-winded and weird, right? Not to mention hard to modify the fixed length without spawning many new types explicitly.

There are also a lot of semi-hidden constraints to be aware of for your custom struct:

  • It contains a single field.
  • The struct` doesn’t specify an explicit layout.
  • The length must be greater than zero (> 0).
  • The target type must be a struct.

Whilst I may the lament the syntactic realisation of this feature, the functionality is a welcome improvement to the language. Again, it shows commitment to performance scenarios which is a useful tool to have in a GC-oriented language.

I do hope Microsoft find a way to make it more palatable to use, but they do point out that it is an expert feature that you are unlikely to want to use explicitly, so perhaps this is a deliberate design decision on their part!

You likely won’t declare your own inline arrays, but you use them transparently when they’re exposed as System.Span<T> or System.ReadOnlySpan<T> objects from runtime APIs.

Alias any type

This is something that, whilst small, is a really great addition to the language.

1
using Point = (int x, int y);

Since value tuples were introduced, they seem to get used quite a lot as anonymous types in private implementation or purely for convenience. Allowing more types to be named will have a positive effect on readability in my opinion.

Interceptors

Interceptors are an experimental language feature that can (in the words of the documentation):

declaratively substitute a call to an interceptable method with a call to itself at compile time.

It is essentially compile-time method switching based on attributes.

There is a full example in the Roslyn repository, which I would encourage you to read because it is a little hard to comprehensively cover in this type of article – it would need its own feature-length post!

This is supposedly a topic of some controversy in the C# community, although I can’t really figure out why. I get that these types of features seem a bit arcane but they are nicely contained in a way that one doesn’t need to use them if they so wish. As someone who likes metaprogramming, the notion of specialising a certain version of a function at compile time seems like a natural thing to want to do. The only objectionable bit in my opinion is that is source generators (and now interceptors) seem like a much-reduced toolset for this type of functionality compared with metaprogramming. Then again, I guess not everyone wants to descend to template hell like we often do in C++!

Talk: Native AOT

This is the talk I was the most excited about! It covered the new Ahead of Time (AOT) Codegen options in .NET 8.

The talk gave a great summary of the pros and cons of the current IL+JIT and new AOT approaches. There was an especially interesting comparison of the different deployment modes and how it affects application size. Impressively, the full AOT code was roughly 10x smaller than the self contained deployment for the demo’d app.

As impressive as this is, there are still a few pitfalls to be aware of. Much like with assembly trimming, programs that make use of highly dynamic features such runtime reflection or IL emitting will not work. Perhaps some effort by library writers to make better use source generators (and potentially interceptors?) will make using similar functionality easier in future.

One killer feature, is the ability to generate entry points in libraries for unmanaged callers. By using this feature, it was demonstrated that it was possible to call .NET Framework functions from modern .NET. Supposedly, it has also been demonstrated that .NET code can be called from around 20 other languages. That’s a great interop win for a language that has long existed in a walled-garden framework.

For me, it is not the short-term that makes this feature exciting. Full AOT is an immensely powerful capability, especially given the increased level of library interop this already introduces. For a long time, native programmers have benefited from highly optimizing compilers that generate lean binaries with linker optimizations. If we can reach even a remotely similar stage of full AOT support in C#, we could well be tooled with a performant, safe language that also has lots of high level features and integrations. Perhaps it is time for me to start writing C# again, it seems like these are exciting times!

Conclusion

A huge thank you must be said to .NET Oxford and the team at Williams for putting the event together. It’s always nice to see community focussed efforts in software and this event really hit the nail on the head. It was a great combination of quick technical talks and fun!

Hopefully it won’t be the last time I attend .NET Oxford, especially with the developments coming to the native code level of the framework. It looks like I have a lot of reading to do!

This post is licensed under CC BY 4.0 by the author.