C# 9.0 から使える Source Generator、良いですね。アプリケーションを作る側の人が Source Generator を作ることは少ないと思いますが、ライブラリを作る側の人には便利ですよね。
基本的な使い方・作り方はここでは解説しません。公式の説明や公式サンプルを見たり、調べたりすれば出てきます。少ないのが残念なのですけれども。
Source Generator で属性を作って、属性の情報からコードを生成したりするというのは典型的な使い方ですね。
// ユーザーコード [GenerateProperty(typeof(SomeType), "Prop1")] partial class TargetType { } // ------------------------ // 生成されたコード partial class TargetType { public SomeType Prop1 { get; set; } }
上記のサンプルコードに特に意味はありません、ただの説明用の例です。GenerateProperty
という属性を使ってプロパティを自動生成してくれるソースジェネレーターを想定してください。(GenerateProperty
という属性自体もソースジェネレーターが生成した属性です。BCL にこんな属性があるわけではないです。)
シンタックスの読み取りはISourceGenerator
から提供される Roslyn の API を使うしかないのですが、ネットで検索しても情報が少なすぎて非常に苦労します。日本語で検索してもほとんど出てきません。英語でもかなり情報は少ないです。
このソースジェネレータ―を作るためには、typeof(SomeType)
の部分から"SomeType"
を読み取ってくる必要があるのですが、その方法の情報がなさ過ぎて苦労したので覚書です。
以下、ソースジェネレータ―の実装の一部。(例なので雑です。実際はもうちょっとちゃんと書いてください)
[2021/02/01 追記] サンプルコードと説明が色々と間違っていました。完全に別のものと勘違いしていました。以下、正しく書きなおしました。
private const string AttributeDef = @"namespace Sample { internal sealed class GeneratePropertyAttribute : System.Attribute { 略 } } "; private readonly Regex _attrRegex = new Regex(@"^(global::)?(Sample\.)?GenerateProperty(Attribute)?$"); public void Execute(GeneratorExecutionContext context) { // ここで最初に属性自体を出力する (略) // シンタックスを総なめして属性を探す var compilation = context.Compilation; var attrs = compilation .SyntaxTrees .SelectMany(s => s.GetRoot().DescendantNodes()) .Where(s => s.IsKind(SyntaxKind.Attribute)) .OfType<AttributeSyntax>() .Where(s => _attrRegex.IsMatch(s.Name.ToString())) .ToArray(); foreach(AttributeSyntax attr in attrs) { var attrSemantic = compilation.GetSemanticModel(attr.SyntaxTree); var expr = attr.ArgumentList.Arguments[0].Expression; // これが typeof の中身の型のフルネーム "SomeNamespace.SomeType" string typeName = attrSemantic .GetSymbolInfo((expr as TypeOfExpressionSyntax).Type) .Symbol!.ToString(); // ↓ 間違い // string typeName = attrSemantic.GetConstantValue(expr).ToString(); // 以下略 (生成コードの出力) } }
SyntaxReceiver
を使う方法もありますが、私はサクッと LINQ で syntax tree を全部なめるのが好きなので LINQ を使いました。どっちが良いのかはわかりませんが、特に問題はないでしょう。
ここで取得できる型名の文字列は、書いたソースコードの expression がそのまま取得できるので、typeof(SomeNamespace.SomeType)
と書いた場合は"SomeNamespace.SomeType"
となります。
セマンティクスをきちんと考慮してくれるため、typeof(SomeType)
と書かれていた場合でも、きちんと名前空間付きのフルネーム"SomeNamespace.SomeType"
を取得できます。
属性引数にtypeof
を指定するのはけっこう頻出パターンだと思うので、日本語で情報があると助かる人が世の中に1人ぐらいはいるんじゃないかな。(いてほしいなぁ)