.NET Tools
Essential productivity kit for .NET and game developers
Interceptors – Using C# 12 in Rider and ReSharper
Welcome to our series, where we take a closer look at the C# 12 language features and how ReSharper and Rider make it easy for you to adopt them in your codebase. If you haven’t yet, download the latest .NET 8 SDK and update your project files!
In this series, we are looking at:
- Primary Constructors
- Interceptors
- Alias Any Type
- Collection Expressions
In this part, we will take a closer look at interceptors. Interceptors are an experimental feature available in preview mode in C# 12. It’s important to note that they may be subject to change or even removal in future releases. Though, as you might conclude from the title, ReSharper and Rider already have support for interceptors in their current state!
Background & Syntax
Interceptors (specification) are intended for advanced source-generator scenarios, particularly in AOT compilation. As of now, they are being used in ASP.NET Core minimal APIs to replace reflection-based mapping of HTTP requests with more tailored and efficient implementations.
Have you been busy with source generators already? Then you’ve probably heard about the “additive-only” rule, which means that they could only add new types or extend partial types. This restriction is now partially lifted (all puns intended) through the introduction of interceptors, which allow specific method calls to be redirected to substitutes without any requirements in user code. This technique allows authors of source generators to seamlessly eliminate inefficient code paths (e.g., reflection) and replace them with handcrafted specialized implementations at compile-time.
Before using interceptors in a project, you need to set a couple of properties:
<PropertyGroup> <!-- ... --> <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);$(RootNamespace).Generated</InterceptorsPreviewNamespaces> <!-- <Features>InterceptorsPreview</Features> --> </PropertyGroup>
The InterceptorsPreviewNamespaces
property defines what namespaces are allowed to contain interceptors, which provides a certain level of awareness. The Features
property was previously used in .NET 8 previews to enable the interceptors feature but is no longer required.
Now, let’s have a look at a simple example:
// User code Console.WriteLine("original"); // 🥱 Console.WriteLine("original"); // 🤯 // Generated code namespace CSharp12 { public static class Interceptor { [System.Runtime.CompilerServices.InterceptsLocation( // TODO: Update absolute file path filePath: "/path/to/file", // TODO: Point to 'Console.{HERE}WriteLine(...)' line: 3, column: 9)] public static void InterceptWriteLine(string? message) { Console.WriteLine($"INTERCEPTED! Original message was '{message}'"); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] file sealed class InterceptsLocationAttribute(string filePath, int line, int column) : Attribute; }
Obviously, the two methods are required to match each others signatures. Interceptors rely on the exact location of an interception, both the absolute physical file path as well as the line and column position within the file. If the above example does not work for you, it’s most certainly because the filePath
, line
, or column
arguments have to be updated.
It’s worth noting, that the InterceptsLocationAttribute
is (currently) not distributed through any BCL assembly. Therefore, source generators are required to define the type themselves. Since multiple source generators could take advantage of the attribute, it should be considered to define the type file-scoped to avoid duplicates.
So, theory class is over – let’s see some ReSharper and Rider!
Visual Cues & Navigation
A central problem of interceptors is that they can give a false impression of what code is actually executed. Just by looking at our source code, we cannot be sure whether a call is being intercepted or not. Even our good-old goto-declaration navigation can lead us in the wrong direction. And once the behavior of the original and intercepting method diverge, the danger will manifest, and we face a real heisenbug!
Both ReSharper and Rider introduce gutter icons and inlay hints as visual cues for intercepted calls:
You can either click the gutter icon or control-click the inlay hint to navigate to the intercepting method. On the intercepting side, you will see the same visual cues, so you can jump back and forth between the two:
In order to show you all about the configuration of interceptor hints, we will need to leave our handcrafted example behind and use a proper source generator included in the dotnet new webapiaot
template.
Inlay Hints Configuration
Interceptor hints can be configured from the Alt-Enter menu or by right-clicking the inlay hint itself. From here, you can choose one of the visibility modes (including push-to-hint) or disable the hints completely:
Since the inlay hint icon is reasonably small, we’ve chosen always as the default visibility. You can change the defaults and modify the list of configured source generators from the settings under Editor | Inlay Hints | C# | Interceptor Hints:
Conclusion
In this post, we’ve discovered interceptors as a promising preview feature and extension to source generators. Try ReSharper 2023.3 or Rider 2023.3 now and never miss to see an interception! If you see any opportunities for additional support, please let us know in the comments below! As always, thank you for reading.
Image credits: Tim Mossholder