はじめに
今回はISymbol.ToDisplay(SymbolDisplayFormat.FullyQualified)
を用いてSymbol
の完全修飾名を取得する方法を紹介したいと思います。
learn.microsoft.com
やり方
ISymbol.ToDisplay(SymbolDisplayFormat.FullyQualified)
を利用することで、完全修飾名を取得することができます。
// global::SourceGenerators.Sample.SampleClass ISymbol symbol = namedSymbol; symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
まずはISymbol.ToDisplayString
については以下のような実装になってます。
namespace Microsoft.CodeAnalysis { /// <summary> /// Represents a symbol (namespace, class, method, parameter, etc.) /// exposed by the compiler. /// </summary> /// <remarks> /// This interface is reserved for implementation by its associated APIs. We reserve the right to /// change it in the future. /// </remarks> [InternalImplementationOnly] public interface ISymbol : IEquatable<ISymbol?> { // ... /// <summary> /// Converts the symbol to a string representation. /// </summary> /// <param name="format">Format or null for the default.</param> /// <returns>A formatted string representation of the symbol.</returns> string ToDisplayString(SymbolDisplayFormat? format = null); // ... } }
引数にSymbolDisplayFormat
を指定するわけですが、これによってどうようなフォーマットで出力されるかを決定します。今回の場合はFullyQualified
というプリセットを利用します。
SymbolDisplayFormat クラス (Microsoft.CodeAnalysis) | Microsoft Learn
namespace Microsoft.CodeAnalysis { /// <summary> /// Describes the formatting rules that should be used when displaying symbols. /// </summary> public class SymbolDisplayFormat { /// <summary> /// Formats the names of all types and namespaces in a fully qualified style (including the global alias). /// </summary> /// <remarks> /// The current behavior will not output the fully qualified style as expected for member symbols (such as properties) because memberOptions is not set. /// For example, MyNamespace.MyClass.MyPublicProperty will return as MyPublicProperty. /// The current behavior displayed here will be maintained for backwards compatibility. /// </remarks> public static SymbolDisplayFormat FullyQualifiedFormat { get; } = new SymbolDisplayFormat( globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes); } }
SymbolDisplayFormat クラス (Microsoft.CodeAnalysis) | Microsoft Learn
結構細かく指定できて、気になる方は公式ドキュメントやコードを読むとよいかと思います。
すべての型と名前空間の名前を完全修飾スタイル (グローバル エイリアスを含む) で書式設定します。
SymbolDisplayFormat.FullyQualifiedFormat プロパティ (Microsoft.CodeAnalysis) | Microsoft Learn
実験
SourceGenerator
の場合、まずISymbol
を実装したSymbol
を取得します。例として雑に[SampleMarker]
を定義し、それが付与されているclass
に対してソースコード生成処理を走らせます。
[Generator(LanguageNames.CSharp)] public class SampleSourceGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { // Attribute定義 context.RegisterPostInitializationOutput(static context => { var source = """ using System; namespace SourceGenerators.Sample; public class SampleMarkerAttribute : Attribute { } """; context.AddSource("SampleMarkerAttribute.Generated.cs", source); }); // 上記で定義したAttributeが付与されているClassを探す var provider = context.SyntaxProvider.ForAttributeWithMetadataName("SourceGenerators.Sample.SampleMarkerAttribute", predicate: static (context, token) => { token.ThrowIfCancellationRequested(); return context is ClassDeclarationSyntax; }, transform: static (context, token) => { token.ThrowIfCancellationRequested(); return context.TargetSymbol as INamedTypeSymbol; }) .Where(x => x != null); context.RegisterSourceOutput(provider, static (context, namedSymbol) => { // 完全修飾名を取得する // global::SourceGenerators.Sample.SampleClass var typeName = namedSymbol?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); }); } }
RegisterSourceOutput
にて、INamedTypeSymbol
(厳密な型はNonErrorNamedTypeSymbol
ですね)からToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
を呼んでみると、以下のような出力が得られました。
// 完全修飾名を取得する // global::SourceGenerators.Sample.SampleClass var typeName = namedSymbol?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
ちなみに余談ですが、型の継承関係は以下のとおりです。
internal sealed class NonErrorNamedTypeSymbol : NamedTypeSymbol internal abstract class NamedTypeSymbol : TypeSymbol, INamedTypeSymbol public interface INamedTypeSymbol : ITypeSymbol public interface ITypeSymbol : INamespaceOrTypeSymbol public interface INamespaceOrTypeSymbol : ISymbol
動作しないケース
実はSymbolDisplayFormat.FullyQualified
を利用しても完全修飾名が得られない場合があります。例えばプロパティ(IPropertySymbol
)に対して実験してみます。
context.RegisterSourceOutput(provider, static (context, propertySymbol) => { // 完全修飾名を取得する // SampleProperty var typeName = propertySymbol?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); });
なんとNameSpace
やClassName
もないプロパティ名のみが返ってきました。その理由も公式ドキュメントに記載されています。
memberOptions が設定されていないため、メンバー シンボル (プロパティなど) の場合、現在の動作では完全修飾スタイルは出力されません。 たとえば、MyNamespace.MyClass.MyPublicProperty は MyPublicProperty として返されます。 ここに表示されている現在の動作は、下位互換性のために維持されます。
SymbolDisplayFormat.FullyQualifiedFormat プロパティ (Microsoft.CodeAnalysis) | Microsoft Learn
結構罠だと思うので要注意です。