第18章 リフレクションとメタプログラミング
18.1 型情報の取得
PHP
<?php
class User {
public string $name;
private int $age;
public function __construct(string $name, int $age)
{
$this->name = $name;
$this->age = $age;
}
public function greet(): string
{
return "Hello, {$this->name}";
}
}
// ReflectionClass
$reflection = new ReflectionClass(User::class);
// クラス情報
echo $reflection->getName(); // User
echo $reflection->getShortName(); // User
echo $reflection->isAbstract(); // false
echo $reflection->isFinal(); // false
// プロパティ
foreach ($reflection->getProperties() as $property) {
echo $property->getName(); // name, age
echo $property->getType(); // string, int
echo $property->isPublic(); // true, false
}
// メソッド
foreach ($reflection->getMethods() as $method) {
echo $method->getName();
echo $method->getReturnType();
// パラメータ
foreach ($method->getParameters() as $param) {
echo $param->getName();
echo $param->getType();
}
}
// インスタンスからの取得
$user = new User("John", 30);
$objReflection = new ReflectionObject($user);
TypeScript
// TypeScript の型情報は実行時に消える
// reflect-metadata ライブラリを使用
import "reflect-metadata";
class User {
@Reflect.metadata("design:type", String)
name: string;
constructor(name: string) {
this.name = name;
}
}
// 実行時の型チェック
const user = new User("John");
console.log(typeof user); // "object"
console.log(user instanceof User); // true
console.log(user.constructor.name); // "User"
// Object.keys でプロパティ名を取得
console.log(Object.keys(user)); // ["name"]
// プロパティの存在確認
console.log("name" in user); // true
console.log(user.hasOwnProperty("name")); // true
// プロトタイプチェーン
console.log(Object.getPrototypeOf(user)); // User.prototype
VB.NET
Class User
Public Property Name As String
Private Age As Integer
Public Sub New(name As String, age As Integer)
Me.Name = name
Me.Age = age
End Sub
Public Function Greet() As String
Return $"Hello, {Name}"
End Function
End Class
' Type オブジェクトの取得
Dim type1 = GetType(User)
Dim type2 = (New User("John", 30)).GetType()
Dim type3 = Type.GetType("MyNamespace.User")
' 型情報
Console.WriteLine(type1.Name) ' User
Console.WriteLine(type1.FullName) ' MyNamespace.User
Console.WriteLine(type1.IsAbstract) ' False
Console.WriteLine(type1.IsSealed) ' False
' プロパティ
For Each prop In type1.GetProperties()
Console.WriteLine(prop.Name)
Console.WriteLine(prop.PropertyType)
Console.WriteLine(prop.CanRead)
Console.WriteLine(prop.CanWrite)
Next
' フィールド(Private含む)
For Each field In type1.GetFields(BindingFlags.NonPublic Or BindingFlags.Instance)
Console.WriteLine(field.Name)
Console.WriteLine(field.FieldType)
Next
' メソッド
For Each method In type1.GetMethods()
Console.WriteLine(method.Name)
Console.WriteLine(method.ReturnType)
' パラメータ
For Each param In method.GetParameters()
Console.WriteLine(param.Name)
Console.WriteLine(param.ParameterType)
Next
Next
18.2 動的メソッド呼び出し
PHP
<?php
$user = new User("John", 30);
// 可変関数/メソッド
$methodName = "greet";
echo $user->$methodName(); // Hello, John
// call_user_func
echo call_user_func([$user, 'greet']);
echo call_user_func_array([$user, 'setName'], ['Jane']);
// Reflection
$reflection = new ReflectionMethod(User::class, 'greet');
echo $reflection->invoke($user);
// プライベートメソッドの呼び出し
$reflection = new ReflectionMethod(User::class, 'privateMethod');
$reflection->setAccessible(true);
$reflection->invoke($user);
// プロパティへのアクセス
$property = new ReflectionProperty(User::class, 'age');
$property->setAccessible(true);
echo $property->getValue($user);
$property->setValue($user, 31);
TypeScript
const user = new User("John");
// ブラケット記法
const methodName = "greet";
(user as any)[methodName]();
// Reflect API
Reflect.get(user, "name");
Reflect.set(user, "name", "Jane");
Reflect.has(user, "name");
// apply
const method = user.greet;
Reflect.apply(method, user, []);
// Function.prototype.call/apply
user.greet.call(user);
user.greet.apply(user, []);
// Proxy
const handler: ProxyHandler<User> = {
get(target, prop) {
console.log(`Getting ${String(prop)}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`Setting ${String(prop)} to ${value}`);
return Reflect.set(target, prop, value);
}
};
const proxy = new Proxy(user, handler);
proxy.name; // ログ出力される
VB.NET
Dim user As New User("John", 30)
' CallByName(レガシー)
Dim result = CallByName(user, "Greet", CallType.Method)
' Reflection
Dim type = user.GetType()
Dim method = type.GetMethod("Greet")
Dim result2 = method.Invoke(user, Nothing)
' パラメータ付き
Dim setMethod = type.GetMethod("SetName")
setMethod.Invoke(user, {"Jane"})
' プライベートメソッド
Dim privateMethod = type.GetMethod("PrivateMethod",
BindingFlags.NonPublic Or BindingFlags.Instance)
privateMethod.Invoke(user, Nothing)
' プロパティへのアクセス
Dim nameProp = type.GetProperty("Name")
Dim nameValue = nameProp.GetValue(user)
nameProp.SetValue(user, "Jane")
' フィールドへの直接アクセス
Dim ageField = type.GetField("Age", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim ageValue = ageField.GetValue(user)
ageField.SetValue(user, 31)
' Dynamic(Option Strict Off の場合)
Option Strict Off
Dim obj As Object = user
obj.Greet() ' 実行時に解決
18.3 動的オブジェクト生成
PHP
<?php
// クラス名からインスタンス生成
$className = "User";
$user = new $className("John", 30);
// Reflection
$reflection = new ReflectionClass(User::class);
$user = $reflection->newInstance("John", 30);
$user = $reflection->newInstanceArgs(["John", 30]);
// パラメータなしコンストラクタ
$user = $reflection->newInstanceWithoutConstructor();
// 無名クラス
$obj = new class {
public string $name = "Anonymous";
};
TypeScript
// コンストラクタ参照
const UserClass = User;
const user = new UserClass("John");
// ファクトリ関数
function createInstance<T>(
ctor: new (...args: any[]) => T,
...args: any[]
): T {
return new ctor(...args);
}
const user2 = createInstance(User, "John");
// Object.create
const userProto = Object.create(User.prototype);
User.call(userProto, "John");
// クラスのレジストリ
const classRegistry = new Map<string, new (...args: any[]) => any>();
classRegistry.set("User", User);
const ClassName = "User";
const UserCtor = classRegistry.get(ClassName);
if (UserCtor) {
const user3 = new UserCtor("John");
}
VB.NET
' Activator
Dim user1 = Activator.CreateInstance(Of User)()
Dim user2 = DirectCast(Activator.CreateInstance(GetType(User)), User)
' パラメータ付き
Dim user3 = Activator.CreateInstance(GetType(User), {"John", 30})
' 型名から(文字列から)
Dim typeName = "MyNamespace.User, MyAssembly"
Dim type = Type.GetType(typeName)
Dim user4 = Activator.CreateInstance(type, {"John", 30})
' Reflection
Dim ctor = GetType(User).GetConstructor({GetType(String), GetType(Integer)})
Dim user5 = ctor.Invoke({"John", 30})
' ジェネリック型の生成
Dim listType = GetType(List(Of))
Dim genericType = listType.MakeGenericType(GetType(User))
Dim list = Activator.CreateInstance(genericType)
18.4 コード生成
PHP
<?php
// eval(非推奨、セキュリティリスク)
$code = 'return $a + $b;';
$result = eval($code);
// 動的関数生成
$fn = create_function('$a, $b', 'return $a + $b;'); // 非推奨
// クロージャで代替
$fn = function($a, $b) { return $a + $b; };
// include でコード読み込み
include 'generated_code.php';
TypeScript
// Function コンストラクタ(非推奨)
const fn = new Function('a', 'b', 'return a + b');
console.log(fn(1, 2)); // 3
// eval(非推奨)
const result = eval('1 + 2');
// 安全なコード生成:テンプレート
function generateClass(name: string, properties: string[]): string {
return `
class ${name} {
${properties.map(p => `${p}: any;`).join('\n ')}
constructor(${properties.map(p => `${p}: any`).join(', ')}) {
${properties.map(p => `this.${p} = ${p};`).join('\n ')}
}
}`;
}
VB.NET
' Reflection.Emit(動的アセンブリ生成)
Dim assemblyName = New AssemblyName("DynamicAssembly")
Dim assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
assemblyName, AssemblyBuilderAccess.Run)
Dim moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule")
' 動的型の定義
Dim typeBuilder = moduleBuilder.DefineType("DynamicUser",
TypeAttributes.Public Or TypeAttributes.Class)
' プロパティの追加
Dim fieldBuilder = typeBuilder.DefineField("_name",
GetType(String), FieldAttributes.Private)
Dim propertyBuilder = typeBuilder.DefineProperty("Name",
PropertyAttributes.None, GetType(String), Nothing)
' ゲッターの定義
Dim getterBuilder = typeBuilder.DefineMethod("get_Name",
MethodAttributes.Public Or MethodAttributes.SpecialName,
GetType(String), Type.EmptyTypes)
Dim getterIL = getterBuilder.GetILGenerator()
getterIL.Emit(OpCodes.Ldarg_0)
getterIL.Emit(OpCodes.Ldfld, fieldBuilder)
getterIL.Emit(OpCodes.Ret)
propertyBuilder.SetGetMethod(getterBuilder)
' 型の作成
Dim dynamicType = typeBuilder.CreateType()
Dim instance = Activator.CreateInstance(dynamicType)
' Source Generators(コンパイル時コード生成、C#のみ)
' VB.NET では利用不可
18.5 メタプログラミングの実用例
依存性注入コンテナ
<?php
class Container {
private array $bindings = [];
public function bind(string $abstract, string $concrete): void {
$this->bindings[$abstract] = $concrete;
}
public function resolve(string $abstract): object {
$concrete = $this->bindings[$abstract] ?? $abstract;
$reflection = new ReflectionClass($concrete);
$constructor = $reflection->getConstructor();
if (!$constructor) {
return new $concrete();
}
$dependencies = [];
foreach ($constructor->getParameters() as $param) {
$type = $param->getType();
if ($type && !$type->isBuiltin()) {
$dependencies[] = $this->resolve($type->getName());
}
}
return $reflection->newInstanceArgs($dependencies);
}
}
' VB.NET
Public Class Container
Private bindings As New Dictionary(Of Type, Type)
Public Sub Bind(Of TAbstract, TConcrete As TAbstract)()
bindings(GetType(TAbstract)) = GetType(TConcrete)
End Sub
Public Function Resolve(Of T)() As T
Return DirectCast(Resolve(GetType(T)), T)
End Function
Private Function Resolve(type As Type) As Object
Dim concrete As Type = Nothing
If Not bindings.TryGetValue(type, concrete) Then
concrete = type
End If
Dim ctor = concrete.GetConstructors().FirstOrDefault()
If ctor Is Nothing Then
Return Activator.CreateInstance(concrete)
End If
Dim dependencies = ctor.GetParameters().
Select(Function(p) Resolve(p.ParameterType)).
ToArray()
Return ctor.Invoke(dependencies)
End Function
End Class
自動シリアライズ
// TypeScript - デコレータベースのシリアライズ
const SERIALIZABLE_KEY = Symbol("serializable");
function Serializable(target: any, propertyKey: string) {
const properties: string[] =
Reflect.getMetadata(SERIALIZABLE_KEY, target) || [];
properties.push(propertyKey);
Reflect.defineMetadata(SERIALIZABLE_KEY, properties, target);
}
function serialize(obj: any): string {
const properties: string[] =
Reflect.getMetadata(SERIALIZABLE_KEY, obj) || [];
const result: Record<string, any> = {};
for (const prop of properties) {
result[prop] = obj[prop];
}
return JSON.stringify(result);
}
class User {
@Serializable name: string = "";
@Serializable email: string = "";
password: string = ""; // シリアライズされない
}
' VB.NET - 属性ベースのシリアライズ
<AttributeUsage(AttributeTargets.Property)>
Public Class SerializableAttribute
Inherits Attribute
End Class
Public Class Serializer
Public Shared Function Serialize(obj As Object) As String
Dim type = obj.GetType()
Dim result As New Dictionary(Of String, Object)
For Each prop In type.GetProperties()
If prop.GetCustomAttribute(Of SerializableAttribute)() IsNot Nothing Then
result(prop.Name) = prop.GetValue(obj)
End If
Next
Return JsonConvert.SerializeObject(result)
End Function
End Class
Public Class User
<Serializable>
Public Property Name As String
<Serializable>
Public Property Email As String
Public Property Password As String ' シリアライズされない
End Class