はじめに
C#12から登場したInterceptors
というコンパイル時に実行するメソッドを置き換えられる機能について紹介したいと思います。
ただしまだ実験的な機能なので、変更される可能性もありますし情報もまだそこまで出てきていません。注意してください。
確認環境
Rider2023.3 EAP8
.Net8.0
C#12
概要
Interceptors
を利用することでコンパイル時に特定のメソッドの呼び出しを別のメソッド呼び出しに置き換えることができます。正直割とやりたい放題が可能になる、かなり強力な機能だと思います。
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.
// DeepL翻訳
インターセプターは、特定のメソッド呼び出しを別のコードに迂回させることができます。属性は実際のソースコードの場所を指定するので、インターセプターは一般的にソースジェネレーターにのみ適しています。インターセプターがどのように機能するかについては、インターセプターに関する提案書を読んでください。
Interceptorsを有効にする
Interceptors
は実験的な機能です。有効にするためには.csproj
に書き込まれているPropertyGroup
の中に以下を付け加えます。
<PropertyGroup> <Features>InterceptorsPreview</Features> </PropertyGroup>
またnamespace
をInterceptorsPreviewNamespaces
に指定する必要もあるそうです。後述しますがInterceptsLocation
属性を利用しているnamespace
を記述してください。(Rider
君ならエラー文に書くべき文章を教えてくれます)
↓namespace Sample
の場合
<PropertyGroup> <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Sample</InterceptorsPreviewNamespaces> </PropertyGroup>
// エラー文
Error CS9137 : 'インターセプター' の実験的な機能は、この名前空間では有効になっていません。プロジェクトに '$(InterceptorsPreviewNamespaces);Sample ' を追加します。
使い方
Source Generator
と一緒に使うことが基本ですが、分かりやすいようシンプルな使い方を見ていきます。
using System.Runtime.CompilerServices; namespace Sample { public static class Program { public static void Main(string[] args) { var c = new C(); // 以下のメソッド呼び出しがコンパイル時に別のメソッド呼び出しに変更されている!? c.InterceptableMethod(1); // (12,15): "interceptor 1" c.InterceptableMethod(1); // (13,15): "other interceptor 1" c.InterceptableMethod(2); // (14,15): "other interceptor 2" // 以下は普通に呼び出し c.InterceptableMethod(1); // "interceptable 1" } } class C { public void InterceptableMethod(int param) { Console.WriteLine($"interceptable {param}"); } } static class D { // lineとcharacterでメソッド名の最初を指定すると、以下のメソッドが呼ばれるように変更される // filePathなども直書きするというよりは、SourceGeneratorを利用して書き込むのが前提で作られているような気がする [InterceptsLocation("/Users/user/RiderProjects/ComsoleAppNet8_0/ComsoleAppNet8_0/Program.cs", line: 12, character: 15)] public static void InterceptorMethod(this C c, int param) { Console.WriteLine($"interceptor {param}"); } [InterceptsLocation("/Users/user/RiderProjects/ComsoleAppNet8_0/ComsoleAppNet8_0/Program.cs", line: 13, character: 15)] [InterceptsLocation("/Users/user/RiderProjects/ComsoleAppNet8_0/ComsoleAppNet8_0/Program.cs", line: 14, character: 15)] public static void OtherInterceptorMethod(this C c, int param) { Console.WriteLine($"other interceptor {param}"); } } } // 「シンボル"InterceptsLocation"を解決できません」とのエラーが出てきてしまうので自身で定義 // おそらく実験的な機能でなくなれば必要なくなるかと namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute { } }
InterceptsLocationAttributeを定義する
コメントにも書きましたがシンボル"InterceptsLocation"を解決できません
とのエラーが出てきてしまうので自分で定義します。
namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute { } }
メソッドを置き換える
InterceptsLocation
属性を記述したメソッドを定義します。
[InterceptsLocation("/Users/user/RiderProjects/ComsoleAppNet8_0/ComsoleAppNet8_0/Program.cs", line: 12, character: 15)] public static void InterceptorMethod(this C c, int param) { Console.WriteLine($"interceptor {param}"); }
またインスタンスメソッドは実は内部的に引数だけでなく自身のインスタンスも渡されています。ですのでthis
キーワードを利用してインスタンスを渡してあげてください。