前回は、型メタデータと System.Type の基本を見ました。
今回は、リフレクションを使って型の中身を詳しく調べます。
メソッド、フィールド、プロパティ、インターフェイス、静的型、ジェネリック型、引数と戻り値を確認する、小さなメタデータビューアを作っていきます。
調査対象の型を用意する
まず、リフレクションで調べるための型を用意します。
namespace ReflectionViewerSample;
public interface IDriveable
{
void Drive(int distance);
}
public class Car : IDriveable
{
private int currentSpeed;
public string Name { get; }
public int CurrentSpeed => currentSpeed;
public Car(string name)
{
Name = name;
}
public void Accelerate(int delta)
{
currentSpeed += delta;
}
public void Drive(int distance)
{
Console.WriteLine($"{Name} drives {distance}km.");
}
public override string ToString()
{
return $"{Name}: {CurrentSpeed}km/h";
}
}
この型には、フィールド、プロパティ、コンストラクター、メソッド、インターフェイス実装が含まれています。
メソッドを調べる
GetMethods() を使うと、型に定義されているメソッドを取得できます。
using System.Reflection;
using ReflectionViewerSample;
Type type = typeof(Car);
MethodInfo[] methods = type.GetMethods(
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.DeclaredOnly);
foreach (MethodInfo method in methods)
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}");
}
BindingFlags は、どのメンバーを取得するかを指定するためのフラグです。
Public: public メンバーを対象にするNonPublic: private や protected なども対象にするInstance: インスタンスメンバーを対象にするStatic: 静的メンバーを対象にするDeclaredOnly: その型で宣言されたメンバーだけを対象にする
DeclaredOnly を外すと、基底クラスから継承されたメソッドも含まれます。
フィールドとプロパティを調べる
フィールドは GetFields()、プロパティは GetProperties() で取得できます。
using System.Reflection;
using ReflectionViewerSample;
Type type = typeof(Car);
Console.WriteLine("フィールド:");
foreach (FieldInfo field in type.GetFields(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.DeclaredOnly))
{
Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}
Console.WriteLine("プロパティ:");
foreach (PropertyInfo property in type.GetProperties(
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.DeclaredOnly))
{
Console.WriteLine($"{property.PropertyType.Name} {property.Name}");
}
プロパティは C# の文法上はフィールドに似て見えますが、メタデータ上はアクセサーメソッドを持つメンバーとして扱われます。
実装しているインターフェイスを調べる
GetInterfaces() を使うと、型が実装しているインターフェイスを取得できます。
using ReflectionViewerSample;
Type type = typeof(Car);
foreach (Type interfaceType in type.GetInterfaces())
{
Console.WriteLine(interfaceType.FullName);
}
プラグイン探索では、この仕組みがよく使われます。
「特定のインターフェイスを実装している型を探す」ことで、外部 DLL から実行対象を見つけられます。
Type contractType = typeof(IDriveable);
bool canDrive = contractType.IsAssignableFrom(typeof(Car));
Console.WriteLine(canDrive);
IsAssignableFrom() は、「指定した型として扱えるか」を判定するために便利です。
その他の基本情報を表示する
型には、種類を判定するためのプロパティがたくさんあります。
using ReflectionViewerSample;
Type type = typeof(Car);
Console.WriteLine($"名前: {type.FullName}");
Console.WriteLine($"クラス: {type.IsClass}");
Console.WriteLine($"抽象型: {type.IsAbstract}");
Console.WriteLine($"public: {type.IsPublic}");
Console.WriteLine($"sealed: {type.IsSealed}");
Console.WriteLine($"値型: {type.IsValueType}");
Console.WriteLine($"列挙型: {type.IsEnum}");
これらを組み合わせると、型の性質を実行時に分類できます。
静的型を調べる
C# の static class は、メタデータ上では abstract かつ sealed な型として表現されます。
namespace ReflectionViewerSample;
public static class Calculator
{
public static int Add(int x, int y) => x + y;
}
using ReflectionViewerSample;
Type type = typeof(Calculator);
Console.WriteLine(type.IsAbstract);
Console.WriteLine(type.IsSealed);
両方が true なら、C# の静的クラスとして定義された可能性があります。
静的メソッドを取得するには、BindingFlags.Static を指定します。
foreach (var method in type.GetMethods(
BindingFlags.Public |
BindingFlags.Static |
BindingFlags.DeclaredOnly))
{
Console.WriteLine(method.Name);
}
ジェネリック型を調べる
ジェネリック型かどうかは IsGenericType で判定できます。
Type type = typeof(Dictionary<string, int>);
Console.WriteLine(type.IsGenericType);
Console.WriteLine(type.GetGenericTypeDefinition());
foreach (Type argument in type.GetGenericArguments())
{
Console.WriteLine(argument.FullName);
}
Dictionary<string, int> のように型引数が具体化された型では、GetGenericArguments() で string と int を取得できます。
未構築のジェネリック型を扱う場合は、次のように書きます。
Type openType = typeof(List<>);
Console.WriteLine(openType.IsGenericTypeDefinition);
このあたりは、DI コンテナーが IRepository<T> のような型を登録・解決するときにも関係します。
メソッドの引数と戻り値を調べる
MethodInfo から、戻り値や引数の情報を取得できます。
using System.Reflection;
using ReflectionViewerSample;
Type type = typeof(Car);
MethodInfo? method = type.GetMethod(nameof(Car.Accelerate));
if (method != null)
{
Console.WriteLine($"戻り値: {method.ReturnType.Name}");
foreach (ParameterInfo parameter in method.GetParameters())
{
Console.WriteLine($"引数: {parameter.ParameterType.Name} {parameter.Name}");
}
}
この仕組みを使うと、API ドキュメント生成、コマンド自動登録、プラグインの検証などができます。
簡易メタデータビューアを作る
ここまでの内容をまとめて、型情報を表示する関数を作ります。
using System.Reflection;
static void PrintMetadata(Type type)
{
Console.WriteLine($"型: {type.FullName}");
Console.WriteLine($"アセンブリ: {type.Assembly.GetName().Name}");
Console.WriteLine($"基底型: {type.BaseType?.FullName}");
Console.WriteLine();
Console.WriteLine("インターフェイス:");
foreach (Type interfaceType in type.GetInterfaces())
{
Console.WriteLine($" {interfaceType.FullName}");
}
Console.WriteLine("プロパティ:");
foreach (PropertyInfo property in type.GetProperties())
{
Console.WriteLine($" {property.PropertyType.Name} {property.Name}");
}
Console.WriteLine("メソッド:");
foreach (MethodInfo method in type.GetMethods(
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.DeclaredOnly))
{
string parameters = string.Join(", ",
method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
Console.WriteLine($" {method.ReturnType.Name} {method.Name}({parameters})");
}
}
使い方です。
PrintMetadata(typeof(ReflectionViewerSample.Car));
PrintMetadata(typeof(Dictionary<string, int>));
型を文字列で受け取るようにすれば、簡単な診断ツールにもできます。
Console.Write("型名を入力してください: ");
string? typeName = Console.ReadLine();
Type? type = Type.GetType(typeName ?? "");
if (type == null)
{
Console.WriteLine("型が見つかりません。");
}
else
{
PrintMetadata(type);
}
まとめ
リフレクションを使うと、型のメソッド、フィールド、プロパティ、インターフェイス、ジェネリック引数などを実行時に調べられます。
この技術は、単なる調査用ではありません。
プラグイン発見、コマンド自動登録、DI、属性駆動の設定、テスト実行など、アプリケーションを拡張しやすくする仕組みの裏側でよく使われています。
次回は、外部アセンブリを動的に読み込み、型を探して実行する「遅延バインディング」の考え方へ進みます。