RoslynのSemantic Modelで型情報を取得

.NET Compiler Platform(Roslyn)で型情報を取得する方法。

取得できる情報は2種類あるというお話。

コードのCompilationやAnalyzerのContextから取得したSemantic Modelからノードの型情報を取得する際には、GetTypeInfoメソッドを使います(object型のGetTypeメソッドではないことに注意!)。このメソッドからは、与えたノードについて2種類の型についての情報を得られます。

一つはそのノードのシンボルの型情報そのもの(Typeプロパティ)で、typeof演算子やGetTypeからの情報と似たものまで取得できたりします。ノードがシンボルでない場合はnullとなるようです。

もう一つは、シンボルが型変換されるときの型情報(ConvertedTypeプロパティ)です。これは、初期化構文や引数として渡すとき等に暗示的変換が行われる場合は、違っているようです。(何もなければ上と同じ情報)

これらの型情報を文字列として表現する場合には、いつものToStringメソッドとToDisplayStringメソッドのどちらかを使います。後者にはフォーマットを指定することができ、フォーマットの自作も可能ですがSymbolDisplayFormatクラスの静的プロパティの中から選ぶのが楽です。

長いので分割してコードを載せていきます。全体のソースコードはこちらから
まずは解析対象となる埋め込まれたコードを整形して載せます。

			using System.Collections;
			using System.Collections.Generic;

			class Class1
			{
				void Method1()
				{
					int foo = 1;
					var array = new object[] {
						1.0 + foo,				// double
						typeof(Class1)			// System.Type
					};

					// first
					Method2(array, array, array);

					// second
					Method2(new Class1[1], null, array as IEnumerable<object>);
				}

				// this is just called, do nothing.
				void Method2(object[] _, IEnumerable<object> __, IEnumerable ___) { }
			}

このプログラムは、3つのそれぞれ型が違う引数のあるダミーのメソッドを呼び出して、どう解析情報が取れるか確かめるように組んであります。また、インスタンス化とnull渡しとasによるキャストではどうなるかも確認します。

次に解析するほうのプログラムです。SemanticModelを得るための決まった処理に続き、配列の初期化、メソッド呼び出し2つの場所での型情報をそれぞれ取得しています。

			var tree = CSharpSyntaxTree.ParseText(code);
			var mscorlib = MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location);
			var compilation = CSharpCompilation.Create("TypeInfo", new[] { tree }, new[] { mscorlib });
			var model = compilation.GetSemanticModel(tree);


			var root = tree.GetRoot();
			var values = root.DescendantNodes().OfType<ArrayCreationExpressionSyntax>().First()
				.ChildNodes().OfType<InitializerExpressionSyntax>().First()
				.ChildNodes();
			foreach (var item in values)
			{
				var info = model.GetTypeInfo(item);
				Console.WriteLine($"{item.WithoutTrivia().ToFullString()} : {info.Type}, "
					+ $"(minimum){info.Type?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}, "
					+ $"(converted){ info.ConvertedType}");
			}
			Console.WriteLine();

			var calling = root.DescendantNodes().OfType<InvocationExpressionSyntax>().First();
			var symbols = calling.ArgumentList.Arguments.Select(a => a.Expression);
			Console.WriteLine("first " + calling.WithoutTrivia().ToString());
			foreach (var item in symbols.Select((v, i) => new { Index = i, Value = v }))
			{
				var info = model.GetTypeInfo(item.Value);
				Console.WriteLine($"No.{item.Index} : {info.Type}, "
					+ $"(minimum){info.Type?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}, "
					+ $"(converted){info.ConvertedType}");
			}
			Console.WriteLine();

			calling = root.DescendantNodes().OfType<InvocationExpressionSyntax>().Skip(1).First();
			symbols = calling.ArgumentList.Arguments.Select(a => a.Expression);
			Console.WriteLine("second " + calling.WithoutTrivia().ToString());
			foreach (var item in symbols.Select((v, i) => new { Index = i, Value = v }))
			{
				var info = model.GetTypeInfo(item.Value);
				Console.WriteLine($"No.{item.Index} : {info.Type}, "
					+ $"(minimum){info.Type?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}, "
					+ $"(converted){info.ConvertedType}");
			}

コードの説明に移る前に、下が出力結果です。

1.0 + foo : double, (minimum)double, (converted)object
typeof(Class1) : System.Type, (minimum)Type, (converted)object

first Method2(array, array, array)
No.0 : object[], (minimum)object[], (converted)object[]
No.1 : object[], (minimum)object[], (converted)System.Collections.Generic.IEnumerable<object>
No.2 : object[], (minimum)object[], (converted)System.Collections.IEnumerable

second Method2(new Class1[1], null, array as IEnumerable<object>)
No.0 : Class1[], (minimum)Class1[], (converted)object[]
No.1 : , (minimum), (converted)System.Collections.Generic.IEnumerable<object>
No.2 : System.Collections.Generic.IEnumerable<object>, (minimum)IEnumerable<object>, (converted)System.Collections.IEnumerable

それぞれの出力の行に関して、左は元の型についてToStringメソッドによるもの、真ん中は元の型についてToDisplayメソッドにMinimumQualifiedFormatを指定した場合、右はConvertedTypeにより変換後の表示です。
配列の初期化部分では、元の型がそれぞれdoubleSystem.Typeなのに対し、変換された方ではそれぞれobjectになっています。
また、メソッド呼び出しでも同様に変わっているのがわかります。なお、ssecond Method2のNo.1では空欄になっていますが、これは解析対象のソース内でnullを渡しているため、こうなっています。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です