🪄
インターフェース未実装クラスを強引にインターフェースに詰めてみる
概要
動的にインターフェースを実装したプロキシクラスを生成し、そこに元のオブジェクトを持たせてインターフェースと接続する仕組みを作成しました。アイデアだけ考えて、実装はGemini君に生成してもらいました。
基本方針としては、
- インターフェース未実装クラスをインターフェースに代入させる
- リフレクション・型生成箇所はなるべくキャッシュする
- IL使用は必要最小限に抑える
のような感じで、あとは動作確認しながら何度か修正して最終的に下記のようになりました。
一応動いてはいますが、まだバグはあるかもしれません。
実装コード
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
/// <summary>
/// インターフェースを介してオブジェクトを操作するプロキシを動的に生成します。
/// Reflection.Emit を使用し、直接呼び出し(Direct Call)を行う IL を生成することで、
/// 実行時のオーバーヘッドを最小限に抑えています。
/// ※構造体を渡した場合、内部でボックス化によるコピーが発生するため
/// プロキシを介した値の変更が元の変数に反映されませんのでサポート外とします。
/// </summary>
public static class InterfaceProxy
{
// プロセス全体で共有される動的アセンブリ。
// Run 指定によりメモリ上でのみ実行し、ディスクへの保存は行いません。
private static readonly AssemblyBuilder _assembly =
AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(nameof(InterfaceProxy)), AssemblyBuilderAccess.Run);
private static readonly ModuleBuilder _module =
_assembly.DefineDynamicModule("MainModule");
// 型生成はコストが高いため、一度生成した Proxy 型は
// インターフェースとターゲット型のペアでキャッシュします。
private static readonly ConcurrentDictionary<(Type, Type), Type> _proxyTypeCache = new();
// 生成した Proxy 型のインスタンス化(new)を高速化するため
// コンストラクタ呼び出しをデリゲートとしてキャッシュします。
private static readonly ConcurrentDictionary<Type, Func<object, object>> _ctorCache = new();
/// <summary>
/// ターゲットオブジェクトを、インターフェース T を実装したプロキシとしてラップします。
/// </summary>
/// <typeparam name="T">実装させるインターフェース型</typeparam>
/// <param name="target">ラップ対象のインスタンス</param>
/// <returns>T を実装した動的プロキシインスタンス</returns>
public static T Create<T>(object target)
{
var interfaceType = typeof(T);
if (!interfaceType.IsInterface)
throw new InvalidOperationException($"{interfaceType.Name} はインターフェースではありません。");
if (target == null)
throw new ArgumentNullException(nameof(target));
// 型キャッシュを確認してプロキシ型を取得
// 無い場合はプロキシ型を生成してキャッシュに登録
var targetType = target.GetType();
if (!_proxyTypeCache.TryGetValue((interfaceType, targetType), out var proxyType))
{
// 値型は未サポート
if (targetType.IsValueType)
throw new NotSupportedException($"値型 ({targetType.Name}) はサポートされていません。参照型(クラス)のみ指定してください。");
proxyType = _proxyTypeCache.GetOrAdd((interfaceType, targetType),
key => BuildType(key.Item1, key.Item2));
}
// コンストラクタデリゲートを取得して生成
var ctor = _ctorCache.GetOrAdd(proxyType, BuildCtor);
return (T)ctor(target);
}
/// <summary>
/// プロキシ型のコンストラクタ呼び出し式をコンパイルします。
/// 生成される処理: (object target) => new ProxyType(target)
/// </summary>
private static Func<object, object> BuildCtor(Type proxyType)
{
var param = Expression.Parameter(typeof(object), "target");
// 構造上、必ず存在するコンストラクタを「!」で取得
var ctorInfo = proxyType.GetConstructor(new[] { typeof(object) })!;
var newExpr = Expression.New(ctorInfo, param);
return Expression.Lambda<Func<object, object>>(newExpr, param).Compile();
}
/// <summary>
/// TypeBuilder を使用して、実行時に新しいクラス型を構築します。
/// </summary>
private static Type BuildType(Type interfaceType, Type targetType)
{
var tb = _module.DefineType(
$"Proxy_{interfaceType.Name}_{targetType.Name}_{Guid.NewGuid():N}",
TypeAttributes.Public);
// インターフェースの実装を宣言
tb.AddInterfaceImplementation(interfaceType);
// 実際のターゲット(Apple, SuperOven 等)を保持する private フィールドを定義
var targetField = tb.DefineField("_target", typeof(object), FieldAttributes.Private);
// --- コンストラクタの定義 ---
// 実装内容: public Proxy(object target) { this._target = target; }
var ctor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(object) });
var il = ctor.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // [Stack: this]
il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)!); // [Stack: (empty)] base() 呼び出し
il.Emit(OpCodes.Ldarg_0); // [Stack: this]
il.Emit(OpCodes.Ldarg_1); // [Stack: this, target]
il.Emit(OpCodes.Stfld, targetField); // [Stack: (empty)] this._target = target
il.Emit(OpCodes.Ret);
// --- メソッドの収集 (継承対応) ---
// インターフェースが他のインターフェースを継承している場合に対応するため、再帰的に収集
// プロパティアクセサやイベントアクセサを含む全メソッドを実装
// Distinct により、複数の継承経路がある場合でも重複実装を防ぐ
var allMethods = interfaceType.GetInterfaces()
.Append(interfaceType)
.SelectMany(i => i.GetMethods())
.Distinct();
foreach (var method in allMethods)
ImplementMethod(tb, method, targetType, targetField);
return tb.CreateTypeInfo().AsType();
}
/// <summary>
/// 各メソッドの実装 IL を生成し、ターゲットへ転送します。
/// </summary>
private static void ImplementMethod(TypeBuilder tb, MethodInfo interfaceMethod, Type targetType, FieldInfo targetField)
{
var parameters = interfaceMethod.GetParameters();
var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
// 1. プロキシクラス上にメソッドを定義
var methodBuilder = tb.DefineMethod(
interfaceMethod.Name,
MethodAttributes.Public | MethodAttributes.Virtual,
interfaceMethod.ReturnType,
paramTypes);
// 2. ターゲットクラスから一致するメソッドを厳密に検索
// 名前とジェネリック性が一致し、かつ各引数の型(ByRef含む)が等価なものを抽出
var targetMethod = targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(m =>
m.Name == interfaceMethod.Name &&
m.IsGenericMethod == interfaceMethod.IsGenericMethod &&
m.GetParameters().Length == parameters.Length &&
m.GetParameters().Zip(parameters, (t, i) => new { Target = t, Interface = i })
.All(p =>
{
var tType = p.Target.ParameterType;
var iType = p.Interface.ParameterType;
// ジェネリック引数(T, T&, T[]など)が含まれる場合
// インターフェースのTとクラスのTは「別の型オブジェクト」なので、
// 文字列による名前比較(T&等)とByRef属性の比較で同一性を確認します。
if (iType.ContainsGenericParameters && tType.ContainsGenericParameters)
{
return tType.Name == iType.Name && tType.IsByRef == iType.IsByRef;
}
// 通常の型(string, int等)は型オブジェクトそのものを比較
return tType == iType;
})
);
// 3. ジェネリックメソッドの場合の型パラメータ転送処理
if (interfaceMethod.IsGenericMethod)
{
var genericArgs = interfaceMethod.GetGenericArguments();
var genericBuilders = methodBuilder.DefineGenericParameters(genericArgs.Select(a => a.Name).ToArray());
for (int i = 0; i < genericArgs.Length; i++)
{
// 制約(where T : class, new() 等)をプロキシメソッドにコピー
genericBuilders[i].SetGenericParameterAttributes(genericArgs[i].GenericParameterAttributes);
genericBuilders[i].SetBaseTypeConstraint(genericArgs[i].GetGenericParameterConstraints().FirstOrDefault(c => !c.IsInterface));
genericBuilders[i].SetInterfaceConstraints(genericArgs[i].GetGenericParameterConstraints().Where(c => c.IsInterface).ToArray());
}
// ターゲット側のメソッドを、プロキシ側で定義した型引数でバインド
if (targetMethod != null && targetMethod.IsGenericMethod)
targetMethod = targetMethod.MakeGenericMethod(genericBuilders);
}
if (targetMethod == null)
throw new MissingMethodException(targetType.Name, interfaceMethod.Name);
// 4. IL 生成:呼び出しの核
var il = methodBuilder.GetILGenerator();
// [手順 A] 呼び出し対象(ターゲット)をロード
il.Emit(OpCodes.Ldarg_0); // [Stack: this(Proxy)]
il.Emit(OpCodes.Ldfld, targetField); // [Stack: this._target(object)]
il.Emit(OpCodes.Castclass, targetType); // [Stack: this._target(TargetType)] 型キャスト(不正な場合はInvalidCastException)
// [手順 B] 引数をスタックに積む
// ref や out は「アドレス」としてスタックに積まれる
for (int i = 0; i < paramTypes.Length; i++)
{
il.Emit(OpCodes.Ldarg, i + 1);
}
// [手順 C] ターゲットメソッドの実行
il.Emit(targetMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, targetMethod);
// [手順 D] 戻り値を返却
il.Emit(OpCodes.Ret);
// インターフェース実装の紐付け(VTable の正当な構築)
tb.DefineMethodOverride(methodBuilder, interfaceMethod);
}
}
使用例
using System;
public interface IKitchenGadget
{
// プロパティ
string ModelName { get; }
// イベント
event EventHandler ProcessCompleted;
// オーバーロード & ref/out
void Cook(string food);
void Cook(int temperature, out bool isSuccess);
// ジェネリックメソッド
void Info<T>(ref T data) where T : class;
}
public class SuperOven
{
public string ModelName => "Oven-Next-2026";
public event EventHandler? ProcessCompleted;
public void Cook(string food)
{
Console.WriteLine($"[Oven] {food} を調理中...");
ProcessCompleted?.Invoke(this, EventArgs.Empty);
}
public void Cook(int temperature, out bool isSuccess)
{
Console.WriteLine($"[Oven] {temperature}度で加熱開始。");
isSuccess = true;
}
public void Info<T>(ref T data) where T : class
{
Console.WriteLine($"[Oven] データ型: {typeof(T).Name}, 値: {data}");
// データを書き換えてみる
if (data is string) data = (T)(object)"更新された情報";
}
}
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("--- InterfaceProxy 全機能テスト ---\n");
// ターゲット作成
var oven = new SuperOven();
// プロキシ作成
var gadget = InterfaceProxy.Create<IKitchenGadget>(oven);
// 1. プロパティの読み取り
Console.WriteLine($"モデル: {gadget.ModelName}");
// 2. イベントの購読
gadget.ProcessCompleted += (s, e) => Console.WriteLine(">> 通知: 調理が完了しました!");
// 3. オーバーロード (String版) とイベント発火
gadget.Cook("ピザ");
// 4. オーバーロード (Int版) と out 引数
bool success;
gadget.Cook(200, out success);
Console.WriteLine($"調理成功: {success}");
// 5. ジェネリックメソッド と ref 引数
string message = "初期状態";
gadget.Info(ref message);
Console.WriteLine($"ref 更新後: {message}");
Console.WriteLine("\n--- テスト完了 ---");
}
}
実行結果
--- InterfaceProxy 全機能テスト ---
モデル: Oven-Next-2026
[Oven] ピザ を調理中...
>> 通知: 調理が完了しました!
[Oven] 200度で加熱開始。
調理成功: True
[Oven] データ型: String, 値: 初期状態
ref 更新後: 更新された情報
--- テスト完了 ---
注意点
- 動的コード生成をしている関係上、NativeAOTでは使用できません。
- 構造体を渡した場合、内部でボックス化によるコピーが発生するため、プロキシを介した値の変更が元の変数に反映されませんのでサポート外とします。
[応用] 複数オブジェクトを一つに合成する
一つが行けるなら、複数オブジェクトを合成出来たりするのでは?などと思ってやってみました。
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
/// <summary>
/// 複数のオブジェクトが同じメソッドを持つ場合の選択戦略を定義します。
/// </summary>
public enum CompositeStrategy
{
/// <summary> 配列の先頭に近いオブジェクトのメソッドを優先します。 </summary>
FirstMatch,
/// <summary> 配列の最後に近いオブジェクトのメソッドを優先します。 </summary>
LastMatch,
/// <summary> 複数のオブジェクトが同じメソッドを持つ場合、例外をスローします。 </summary>
ErrorOnConflict
}
/// <summary>
/// 複数のオブジェクトを合成し、インターフェースを介して操作するプロキシを動的に生成します。
/// Reflection.Emit を使用し、特定のターゲットへ直接転送(Direct Call)を行う IL を生成することで、
/// 実行時のオーバーヘッドを最小限に抑えています。
/// ※構造体を渡した場合、内部でボックス化によるコピーが発生するため
/// プロキシを介した値の変更が元の変数に反映されませんのでサポート外とします。
/// </summary>
public static class CompositeInterfaceProxy
{
// プロセス全体で共有される動的アセンブリ。
// Run 指定によりメモリ上でのみ実行し、ディスクへの保存は行いません。
private static readonly AssemblyBuilder _assembly =
AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(nameof(CompositeInterfaceProxy)), AssemblyBuilderAccess.Run);
private static readonly ModuleBuilder _module =
_assembly.DefineDynamicModule("MainModule");
// 型生成はコストが高いため、一度生成した Proxy 型はキャッシュ
// キー: (インターフェース型, ターゲット型の組み合わせ, 戦略)
private static readonly ConcurrentDictionary<(Type, string, CompositeStrategy), Type> _proxyTypeCache = new();
// 生成した Proxy 型のインスタンス化(new)を高速化するため
// コンストラクタ呼び出しをデリゲートとしてキャッシュします。
private static readonly ConcurrentDictionary<Type, Func<object[], object>> _ctorCache = new();
/// <summary>
/// 複数のターゲットオブジェクトを合成し、インターフェース T を実装したプロキシとしてラップします。
/// </summary>
/// <typeparam name="T">実装させるインターフェース型</typeparam>
/// <param name="targets">ラップ対象のインスタンス群</param>
/// <param name="strategy">メソッド重複時の選択戦略(基本は ErrorOnConflict を推奨)</param>
/// <returns>T を実装した動的プロキシインスタンス</returns>
public static T Create<T>(object[] targets, CompositeStrategy strategy = CompositeStrategy.ErrorOnConflict)
{
var interfaceType = typeof(T);
if (!interfaceType.IsInterface)
throw new InvalidOperationException($"{interfaceType.Name} はインターフェースではありません。");
if (targets == null || targets.Length == 0)
throw new ArgumentException("ターゲットオブジェクトを1つ以上指定してください。");
// null排除
var validTargets = targets?.Where(t => t != null).ToArray();
if (validTargets == null || validTargets.Length == 0)
throw new ArgumentException("有効なターゲットオブジェクトを1つ以上指定してください。");
// ターゲット型の組み合わせを文字列キー化
var targetTypes = validTargets.Select(t => t.GetType()).ToArray();
var typesKey = string.Join("|", targetTypes.Select(t => t.FullName));
// 型キャッシュを確認してプロキシ型を取得
// 無い場合はプロキシ型を生成してキャッシュに登録
if (!_proxyTypeCache.TryGetValue((interfaceType, typesKey, strategy), out var proxyType))
{
// 値型は未サポート
var invalidTypes = targetTypes
.Where(t => t.IsValueType)
.Select(t => t.Name)
.Distinct()
.ToList();
if (invalidTypes.Any())
{
throw new NotSupportedException(
$"値型 ({string.Join(", ", invalidTypes)}) はサポートされていません。参照型(クラス)のみ指定してください。");
}
proxyType = _proxyTypeCache.GetOrAdd((interfaceType, typesKey, strategy),
key => BuildType(interfaceType, targetTypes, strategy));
}
// コンストラクタデリゲートを取得して生成
var ctor = _ctorCache.GetOrAdd(proxyType, BuildCtor);
return (T)ctor(validTargets);
}
/// <summary>
/// プロキシ型のコンストラクタ呼び出し式をコンパイルします。
/// 生成される処理: (object[] targets) => new ProxyType(targets)
/// </summary>
private static Func<object[], object> BuildCtor(Type proxyType)
{
var param = Expression.Parameter(typeof(object[]), "targets");
// 構造上、必ず存在するコンストラクタを「!」で取得
var ctorInfo = proxyType.GetConstructor(new[] { typeof(object[]) })!;
var newExpr = Expression.New(ctorInfo, param);
return Expression.Lambda<Func<object[], object>>(newExpr, param).Compile();
}
/// <summary>
/// TypeBuilder を使用して、実行時に新しいクラス型を構築します。
/// </summary>
private static Type BuildType(Type interfaceType, Type[] targetTypes, CompositeStrategy strategy)
{
var tb = _module.DefineType(
$"CompositeProxy_{interfaceType.Name}_{Guid.NewGuid():N}",
TypeAttributes.Public);
tb.AddInterfaceImplementation(interfaceType);
// ターゲットの配列を保持する private フィールドを定義
var targetsField = tb.DefineField("_targets", typeof(object[]), FieldAttributes.Private);
// --- コンストラクタの定義 ---
// 実装内容: public Proxy(object[] targets) { this._targets = targets; }
var ctor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(object[]) });
var il = ctor.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // [Stack: this]
il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)!); // [Stack: (empty)] base() 呼び出し
il.Emit(OpCodes.Ldarg_0); // [Stack: this]
il.Emit(OpCodes.Ldarg_1); // [Stack: this, target]
il.Emit(OpCodes.Stfld, targetsField); // [Stack: (empty)] this._target = target
il.Emit(OpCodes.Ret);
// --- メソッドの収集 (継承対応) ---
// インターフェースが他のインターフェースを継承している場合に対応するため、再帰的に収集
// プロパティアクセサやイベントアクセサを含む全メソッドを実装
// Distinct により、複数の継承経路がある場合でも重複実装を防ぐ
var allMethods = interfaceType.GetInterfaces()
.Append(interfaceType)
.SelectMany(i => i.GetMethods())
.Distinct();
foreach (var method in allMethods)
{
// 全ターゲットの中から、当該メソッドを持つ候補を抽出
var candidates = targetTypes
.Select((type, index) => new { type, index, targetMethod = FindMatchingMethod(type, method) })
.Where(x => x.targetMethod != null)
.ToList();
if (candidates.Count == 0)
throw new MissingMethodException($"{interfaceType.Name}.{method.Name} を実装するオブジェクトがターゲット内に存在しません。");
// 合成元メソッドが重複していた場合、戦略に基づいて採用するターゲットを1つ決定
var selected = strategy switch
{
CompositeStrategy.FirstMatch => candidates.First(),
CompositeStrategy.LastMatch => candidates.Last(),
CompositeStrategy.ErrorOnConflict => candidates.Count > 1 ? throw new AmbiguousMatchException(
$"メソッド '{method.Name}' が複数のターゲットで重複しています。({string.Join(", ", candidates.Select(c => c.type.Name))})"
) : candidates.First(),
_ => throw new NotImplementedException($"Strategy '{strategy}' は未実装です。")
};
ImplementMethod(tb, method, selected.targetMethod!, selected.type, targetsField, selected.index);
}
return tb.CreateTypeInfo().AsType();
}
/// <summary>
/// 各メソッドの実装 IL を生成し、特定のターゲットインデックスへ転送します。
/// </summary>
private static void ImplementMethod(TypeBuilder tb, MethodInfo interfaceMethod, MethodInfo targetMethod, Type targetType, FieldInfo targetsField, int targetIndex)
{
var parameters = interfaceMethod.GetParameters();
var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
// 1. プロキシクラス上にメソッドを定義
var methodBuilder = tb.DefineMethod(
interfaceMethod.Name,
MethodAttributes.Public | MethodAttributes.Virtual,
interfaceMethod.ReturnType,
paramTypes);
// ジェネリックパラメータの転送
if (interfaceMethod.IsGenericMethod)
{
var genericArgs = interfaceMethod.GetGenericArguments();
var genericBuilders = methodBuilder.DefineGenericParameters(genericArgs.Select(a => a.Name).ToArray());
for (int i = 0; i < genericArgs.Length; i++)
{
// 制約(where T : class, new() 等)をプロキシメソッドにコピー
genericBuilders[i].SetGenericParameterAttributes(genericArgs[i].GenericParameterAttributes);
genericBuilders[i].SetBaseTypeConstraint(genericArgs[i].GetGenericParameterConstraints().FirstOrDefault(c => !c.IsInterface));
genericBuilders[i].SetInterfaceConstraints(genericArgs[i].GetGenericParameterConstraints().Where(c => c.IsInterface).ToArray());
}
// ターゲット側のメソッドを、プロキシ側で定義した型引数でバインド
if (targetMethod.IsGenericMethod)
targetMethod = targetMethod.MakeGenericMethod(genericBuilders);
}
// IL 生成
var il = methodBuilder.GetILGenerator();
// [手順 A] ターゲット配列から特定の要素をロードしてキャスト
il.Emit(OpCodes.Ldarg_0); // [Stack: this]
il.Emit(OpCodes.Ldfld, targetsField); // [Stack: this._targets]
il.Emit(OpCodes.Ldc_I4, targetIndex); // [Stack: this._targets, index]
il.Emit(OpCodes.Ldelem_Ref); // [Stack: object]
il.Emit(OpCodes.Castclass, targetType); // [Stack: TargetType]
// [手順 B] 引数をスタックに積む
// ref や out は「アドレス」としてスタックに積まれる
for (int i = 0; i < paramTypes.Length; i++)
{
il.Emit(OpCodes.Ldarg, i + 1);
}
// [手順 C] ターゲットメソッドの実行
il.Emit(targetMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, targetMethod);
// [手順 D] 戻り値を返却
il.Emit(OpCodes.Ret);
// インターフェース実装の紐付け(VTable の正当な構築)
tb.DefineMethodOverride(methodBuilder, interfaceMethod);
}
/// <summary>
/// ターゲット型からインターフェースメソッドと一致するシグネチャを検索します。
/// </summary>
private static MethodInfo? FindMatchingMethod(Type targetType, MethodInfo interfaceMethod)
{
var parameters = interfaceMethod.GetParameters();
return targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(m =>
m.Name == interfaceMethod.Name &&
m.IsGenericMethod == interfaceMethod.IsGenericMethod &&
m.GetParameters().Length == parameters.Length &&
m.GetParameters().Zip(parameters, (t, i) => new { T = t.ParameterType, I = i.ParameterType })
.All(p => p.T.Name == p.I.Name && p.T.IsByRef == p.I.IsByRef)
);
}
}
使用例
using System;
// 1. 定義:統合したいインターフェース
public interface ISuperDevice
{
void TurnOn(); // Lightが担当
void PlayMusic(); // Radioが担当
string GetStatus(); // 両方が持っている(衝突が発生する)
}
// 2. 実装:個別の機能を持つクラス群
public class Light
{
public void TurnOn() => Console.WriteLine("電球が点灯しました。");
public string GetStatus() => "照明は正常です。";
}
public class Radio
{
public void PlayMusic() => Console.WriteLine("音楽を再生します:FM 80.2MHz");
public string GetStatus() => "ラジオは受信良好です。";
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== CompositeInterfaceProxy 実行サンプル ===\n");
var myLight = new Light();
var myRadio = new Radio();
// ---------------------------------------------------------
// パターンA:重複時にエラーを出す(デフォルト)
// ---------------------------------------------------------
try
{
Console.WriteLine("パターンA: デフォルト(ErrorOnConflict)で合成を試行...");
// Light と Radio は両方 GetStatus を持っているため、ここでエラーになる
var deviceError = CompositeInterfaceProxy.Create<ISuperDevice>(new object[] { myLight, myRadio });
}
catch (System.Reflection.AmbiguousMatchException ex)
{
Console.WriteLine($"期待通りのエラー: {ex.Message}");
}
Console.WriteLine("\n---------------------------------------------------------");
// ---------------------------------------------------------
// パターンB:先頭優先(FirstMatch)
// ---------------------------------------------------------
Console.WriteLine("パターンB: FirstMatch(Lightを優先)で合成...");
var lightFirstDevice = CompositeInterfaceProxy.Create<ISuperDevice>(
new object[] { myLight, myRadio },
CompositeStrategy.FirstMatch
);
lightFirstDevice.TurnOn(); // Lightの実装
lightFirstDevice.PlayMusic(); // Radioの実装
Console.WriteLine($"ステータス: {lightFirstDevice.GetStatus()}"); // Light側が呼ばれる
Console.WriteLine("\n---------------------------------------------------------");
// ---------------------------------------------------------
// パターンC:末尾優先(LastMatch)
// ---------------------------------------------------------
Console.WriteLine("パターンC: LastMatch(Radioを優先)で合成...");
var radioFirstDevice = CompositeInterfaceProxy.Create<ISuperDevice>(
new object[] { myLight, myRadio },
CompositeStrategy.LastMatch
);
radioFirstDevice.TurnOn();
radioFirstDevice.PlayMusic();
Console.WriteLine($"ステータス: {radioFirstDevice.GetStatus()}"); // Radio側が呼ばれる
Console.WriteLine("\n=== 実行完了 ===");
Console.ReadLine();
}
}
実行結果
=== CompositeInterfaceProxy 実行サンプル ===
パターンA: デフォルト(ErrorOnConflict)で合成を試行...
期待通りのエラー: メソッド 'GetStatus' が複数のターゲットで重複しています。(Light, Radio)
---------------------------------------------------------
パターンB: FirstMatch(Lightを優先)で合成...
電球が点灯しました。
音楽を再生します:FM 80.2MHz
ステータス: 照明は正常です。
---------------------------------------------------------
パターンC: LastMatch(Radioを優先)で合成...
電球が点灯しました。
音楽を再生します:FM 80.2MHz
ステータス: ラジオは受信良好です。
=== 実行完了 ===
出来ちゃいました。
発想次第で、色々他にも面白い事ができるかもしれません。
今回作ってみたものは、とりあえずこちらに置いておきます。
個人的見解など
動的コード生成で出来るかという単なる実証実験なので、実用性を考えるとソースジェネレータにした方が筋はいい気はします。
実際、そのようなライブラリで Avatar というのが存在しますが、もう既に Public archive になっちゃってますね。特殊な用途しか思いつかないので、あまり需要はないのかもしれません。
ただ、人があまりやらない事やるのって楽しいですよね。
Discussion