New C# 12 preview features

Kathleen Dollard

Visual Studio 17.7 Preview 3 and .NET 8 Preview 6 continue the evolution of C# 12. This preview includes features designed to lay the groundwork for future performance enhancements. Easy access to inline arrays will allow libraries to use them in more places without effort on your part. This preview debuts an experimental feature called interceptors that allow generators to reroute code, such as to provide context specific optimization. Finally, nameof is enhanced to work in more places.

You can get C# 12 by installing the latest Visual Studio preview or the latest version of the .NET SDK. To check out C# 12 features, you’ll need to set the language version of your project to preview:

<PropertyGroup>
   <LangVersion>preview</LangVersion>
</PropertyGroup>

Since they are experimental, interceptors require an additional flag in your project file.

nameof accessing instance members

The nameof keyword now works with member names, including initializers, on static members, and in attributes:

internal class NameOf
{
    public string S { get; } = "";
    public static int StaticField;
    public string NameOfLength { get; } = nameof(S.Length);
    public static void NameOfExamples()
    {
        Console.WriteLine(nameof(S.Length));
        Console.WriteLine(nameof(StaticField.MinValue));
    }
    [Description($"String {nameof(S.Length)}")]
    public int StringLength(string s)
    { return s.Length; }
}

You can learn more at What’s new in C# 12.

Inline arrays

The InlineArrayAttribute was introduced to the runtime in a previous .NET 8 preview. This is an advanced feature that will be used primarily by the compiler, .NET libraries and some other libraries. The attribute identifies a type that can be treated as a contiguous sequence of primitives for efficient, type-safe, overrun-safe indexable/sliceable inline data. The .NET libraries improve performance of your application and tools using inline arrays.

The compiler creates different IL to access inline arrays. This results in a few restrictions, such as list patterns not being supported. In most cases, you access inline arrays the same as other arrays. The different IL creates performance gains without changing your code:

private static void InlineArrayAccess(Buffer10<int> inlineArray)
{
    for (int i = 0; i < 10; i++)
    {
        inlineArray[i] = i * i;
    }
    foreach (int i in inlineArray)
    {
        Console.WriteLine(i);
    }
}

Most folks will consume inline arrays, rather than create them. But, it’s nice to understand how things work. Inline arrays are fast because they rely on an exact layout of a specified length. An inline array is a type with a single field and marked with the InlineArrayAttribute that specifies the length of the array. In the type used in the previous example, the runtime creates storage for exactly ten elements in Buffer10<T> due to the attribute parameter:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
    private T _element0;
}

You can learn more at What’s new in C# 12.

Interceptors

This preview introduces an experimental feature called interceptors. It’s intended for advanced scenarios, particularly allowing better ahead of time compilation (AOT). As an experimental part of .NET 8, it may be changed or removed in a future version. Thus, it should not be used in production.

Interceptors allow specific method calls to be rerouted to different code. Attributes specify the actual source code location so interceptors are generally appropriate only for source generators. You can read the interceptors proposal to learn more about how interceptors work.

Because interceptors are an experimental feature, you’ll need to explicitly enable them in your project file:

<PropertyGroup>
   <Features>InterceptorsPreview</Features>
</PropertyGroup>

Interceptors enable exciting code patterns. A few examples are:

  • Calls that are known at compile time, like Regex.IsMatch(@"a+b+") with a constant pattern can be intercepted to use statically-generated code for optimization that is friendly to AOT.
  • ASP.NET Minimal API calls like app.MapGet("/products", handler: (int? page, int? pageLength, MyDb db) => { ... }) can be intercepted to register a statically-generated thunk which calls the user’s handler directly, skipping an allocation and indirection.
  • In vectorization, where foreach loops contain calls to user methods, the compiler can rewrite code to check for and use relevant intrinsics at runtime, but fall back to the original code if those intrinsics aren’t available.
  • Static dependency graph resolution for dependency injection, where provider.Register<MyService>() can be intercepted.
  • Calls to query providers could be intercepted to offer translation to another language (e.g. SQL) at compile time, rather than evaluating expression trees to translate at runtime.
  • Serializers could generate type specific (de)serialization based on the concrete type of calls like Serialize<MyType>(), all at compile time.

Most programmers will not use interceptors directly, but we hope they will play a significant role in our journey to make your applications run faster and be easier to deploy. Interceptors are expected to remain experimental in the C# 12/.NET 8 release and may be included in a future version of C#.

Summary

You can find more information about all of the features introduced so far at the What’s new in C# 12 page of Microsoft Learn and track the evolution of C# 12 features at the Roslyn Feature Status page.

You can check out the latest C# 12 features by downloading the latest Visual Studio preview or the latest version of the .NET SDK, and setting LangVersion to preview in your project file.

Let us know what you think!

34 comments

Comments are closed. Login to edit/delete your existing comments

  • Paulo Pinto 2

    While the feature is welcomed, I don’t really get why InlineArray is an attribute instead of a proper language feature, like every other AOT compiled language with support for value types.

    Reading the interceptors proposal, it feels like that what is really missing is doing a proper refactoring for AOT support, like the Java ecosystem has been doing for GraalVM and OpenJ9 support, or actually think about AOP frameworks like Microsoft Fakes, or the now gone Phoenix/LOOM.NET.

    • Kathleen DollardMicrosoft employee 1

      We may add language support for creating inline arrays in the future. We felt the most important first step was to make them easy to use, since many more people will be using them than creating them.

    • Andrew Witte 1

      Totally agree.
      Why is C# getting temp patch features added in like InlineArray that only solve niche problems and not the underlining issue?
      Support for fixed array struct fields is whats needed for both unsafe & managed code. If this means older .NET runtimes can’t handle the new C# version so be it. Something is fundamentally wrong with value types in .NET or something at the IL level.

      • Kathleen DollardMicrosoft employee 0

        Can you clarify what scenarios you think are not yet covered between this feature, span, etc.

  • Prem Shah 9

    What is the news on field keyword for property. Will it comming in C#12

    • Kathleen DollardMicrosoft employee 0

      I apologize for the delay. We met yesterday on our current best estimate of what will be in C# 12: https://github.com/dotnet/roslyn/discussions/69074

      Unfortunately, field access in auto-properties (renamed from “semi-autoproperties”) will not be in C# 12.

  • JinShil 4

    It seems Inline Arrays and Interceptors are compensating from something lacking in the language’s design and/or implementation. Couldn’t Inline Arrays be implemented using unsafe code and better codegen? Similarly, if you committed to implementing compile-time evaluation into the language like D and Zig, I think Interceptors would be unnecessary. The fact that these features are needed to achieve your goals doesn’t smell right.

    Also, I continue to recommend following through on completing existing features that remain in limbo like Default Interface Methods base(T), the file access modifier, just to name two. I’d rather see the language implementation tightened up, over features like these.

    • Kathleen DollardMicrosoft employee 0

      Please ping the issues, or create new ones, on the additions you want to existing features at https://github.com/dotnet/csharplang.

      We use the input we receive there to prioritize future work.

      • anonymous 1

        this comment has been deleted.

  • Praveen Potturu 0

    Regarding interceptors, do they get to see code generated by the source generators? I am asking because I am working on a source generator that generates calls to MapGet, MapPost etc.

    • Kathleen DollardMicrosoft employee 0

      Interceptors use normal source generators. So, like all other generators, their source generators cannot see the output of other source generators. However, they are also normal C# code (with an attribute that leads to special handling), so the code within an interceptor can access the output of other source generators.

  • Renee GA 3

    Any chance that extensions still gets into C#12?

    • Kathleen DollardMicrosoft employee 0

      I apologize for the delay. We met yesterday on our current best estimate of what will be in C# 12: https://github.com/dotnet/roslyn/discussions/69074

      Unfortunately, implicit and explicit extensions (renamed from “roles and extensions”) will not be in C# 12.

  • W L 6

    still no Discriminated Unions

    • Kathleen DollardMicrosoft employee 1

      C# 12 will not include Discriminated Unions.

      I anticipate that we will continue to work on this in C# 13.

  • MgSam 8

    Surprised Interceptors made it to a Preview state given how negative the feedback was on Github.

    I think Microsoft still needs to go back to the drawing board here. Source generators suck. They have terrible performance, they’re hard to write, near impossible to debug, run non-deterministically, and have limited use cases. They are a failure and tweaking around the edges to try and eek out slightly better perf doesn’t seem like its going to fix most of their problems.

    I’d love to see you guys revisit the replace/original proposal and figure out the tooling issues which stopped this from becoming a proper language feature rather than an ugly compiler hack.

    • Sedlmair, Martin (SHS DI CT R&D CTC SA) 1

      Agreed. Since in every proposal that could be a language feature, the C# language team proposes to write analyzers or source generators instead too offload the lack of development resources in the c# language team to the regular user. If this is the way, then I agree that development analyzers and source generators must be easier and not having to restart VS again and again so that the new analyzer is getting active.

      • Alexander Gayko 1

        hey Martin,
        i solved this issue and released the “fix” as an extension:
        https://marketplace.visualstudio.com/items?itemName=AlexanderGayko.AutoUpdateAssemblyName
        please promote it 😛

        Having said that, the reasoning behind generators working as they do and being dev’ed as they do (with unit tests in mind) is not wrong – it’s just inconvenient for people wanting to build (and move on) their own SGs for themselves. Please chime in in the discussion over in the c# discord in the roslyn channel.

        thanks,
        Alex

    • Paulo Pinto 3

      And the documentation, the last time I checked it was basically “read the code from those samples over there”.

  • Martin Sedlmair 5

    Thanks for the hard work. But to be honest I can see nothing I can benefit from. Most things are syntactic sugar which of course help and I admit that there have been things (like records) I didn’t find useful at the beginning. But I can see nothing in https://learn.microsoft.com/en-my/dotnet/csharp/whats-new/csharp-12 that allows me to do things I can currently not do, things, that help me to improve code quality/readability and to prevent people from creating wrong code.

    I know no one wants to hear this when pointing to C++ and I cannot agree more that C# should not be like C++, but there structures in C++ like move semantics, copy semantics, deterministic cleanup etc. that are currently simply not available in C#. These things are shifted to a later time maybe for more discussion etc. or are even blocked because source generators or analyzers could do the trick. For example roles and extensions would be a tremendous improvement but they are discussed away from one version to the next.

    For marketing reasons I understand that you need to bring in at least lets say 5 (?) new features. Currently it seems you always decide for the simplest. So please invest into at least one larger feature or is it really the lack of resources in the language team?

    • BellarmineHead 2

      So please invest into at least one larger feature

      Discriminated unions. Grasp the nettle. Deliver something actually useful for a change! Do it.

      • Kathleen DollardMicrosoft employee 0

        We did design work in this cycle on roles and extensions that led to our now considering them as implicit and explicit extensions. We think this perspective will help us move forward in this important space.

        We also worked on discriminated unions, and hope both can move forward next year.

        You can find out more about his process in the LDM notes: https://github.com/dotnet/csharplang/tree/main/meetings/2023

  • gc y 0

    Will C# 12 support ref struct to implement interface?

  • pot summ 1

    Missing xml closed tags

    <PropertyGroup>
       <Features>InterceptorsPreview<Features>
    </PropertyGroup>
    
    • Kathleen DollardMicrosoft employee 0

      Thanks! Fixed

Feedback usabilla icon