C#で動的にインターフェイスを実装するクラスを作成する
はじめに
以前の記事でコードを読み進める中で登場したTypeBuilderクラスについて、全く知見がなかったため、まずシンプルなコード例を実際に作ってみました。
全コードは次章に記載しております。
サンプル
IMyDynamicTypeを実現するMyDynamicTypeを作成、インスタンス化する
using System.Reflection;
using System.Reflection.Emit;
namespace DemoAssemblyBuilder
{
/// <summary>
/// 動作確認用インターフェイス
/// </summary>
public interface IMyDynamicType
{
int Number { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
// 参考元:
// https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.typebuilder?view=net-7.0
// 以下のクラスを動的に作成します
/*
namespace MyExample
{
public class MyDynamicType : IMyDynamicType
{
private int m_number;
public int Number
{
get { return m_number; }
set { m_number = value; }
}
}
}
*/
// アセンブリの定義
AssemblyName assemblyName = new AssemblyName("MyExample");
AssemblyBuilder assemblyBuilder =
AssemblyBuilder.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.Run);
// モジュールの定義
// モジュール名は多くの場合アセンブリ名と同じにすると良い
ModuleBuilder moduleBuilder =
assemblyBuilder.DefineDynamicModule(assemblyName.Name!);
// 型情報の定義
// 以降、MyDynamicTypeを作っていきます
TypeBuilder typeBuilder = moduleBuilder.DefineType(
"MyDynamicType",
TypeAttributes.Public);
// IMyDynamicTypeを実現します
Type myInterfaceType = typeof(IMyDynamicType);
typeBuilder.AddInterfaceImplementation(myInterfaceType);
// m_numberを定義します
FieldBuilder fbNumber = typeBuilder.DefineField(
"m_number",
typeof(int),
FieldAttributes.Private);
// Numberプロパティを定義します
// ただし、本サンプルの場合は無くても動作しました
// PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(nameof(IMyDynamicType.Number), PropertyAttributes.HasDefault, typeof(int), null);
// get, setの属性を定義します
// 以下のURLにある通り、MethodAttributes.Virtual属性が必要です
// https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.typebuilder.addinterfaceimplementation?view=net-7.0
MethodAttributes getSetAttr = MethodAttributes.Public |
MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual;
// まずgetterを定義します
MethodBuilder mbNumberGetAccessor = typeBuilder.DefineMethod(
"get_Number",
getSetAttr,
typeof(int),
Type.EmptyTypes);
// ILGeneratorが動的アセンブリを作るために必要なものです
// https://learn.microsoft.com/ja-jp/dotnet/api/system.reflection.emit.ilgenerator?view=net-7.0
ILGenerator numberGetIL = mbNumberGetAccessor.GetILGenerator();
// 以下は引数なし、フィールド読み込み、値の返却を行なっています
numberGetIL.Emit(OpCodes.Ldarg_0);
numberGetIL.Emit(OpCodes.Ldfld, fbNumber);
numberGetIL.Emit(OpCodes.Ret);
// setterを定義します
MethodBuilder mbNumberSetAccessor = typeBuilder.DefineMethod(
"set_Number",
getSetAttr,
null,
new Type[] { typeof(int) });
ILGenerator numberSetIL = mbNumberSetAccessor.GetILGenerator();
// 読み込んだ第一引数をフィールドに設定して返しています
numberSetIL.Emit(OpCodes.Ldarg_0);
numberSetIL.Emit(OpCodes.Ldarg_1);
numberSetIL.Emit(OpCodes.Stfld, fbNumber);
numberSetIL.Emit(OpCodes.Ret);
// プロパティビルダーに設定することでプロパティが定義できるはずですが、本サンプルではなくても動作しました
//propertyBuilder.SetGetMethod(mbNumberGetAccessor);
//propertyBuilder.SetSetMethod(mbNumberSetAccessor);
// 作成したgetter, setterを実現します
typeBuilder.DefineMethodOverride(mbNumberGetAccessor, typeof(IMyDynamicType).GetMethod("get_Number")!);
typeBuilder.DefineMethodOverride(mbNumberSetAccessor, typeof(IMyDynamicType).GetMethod("set_Number")!);
// 以降、動作確認
Type myMockType = typeBuilder.CreateType()!;
IMyDynamicType mock = (IMyDynamicType)Activator.CreateInstance(myMockType)!;
mock.Number = 3;
Console.WriteLine($"{nameof(mock.Number)} : " + mock.Number);
Console.ReadKey();
}
}
}
個人的な学習ポイント
サンプルのようにコードを実際に書いてみると、色々と気づかされることがありました。
(実際にはそのように学んでいたかもしれませんが、再認識しました)
- インターフェイスの実装は、少なくとも属性から推察するにvirtualメソッドをオーバーライドしているのと変わらない
- getter, setterは内部的にはメソッドと何ら変わらず、省略形でしかない
終わりに
Moqのように動的に型情報からインスタンス化できることが不思議で、車輪の再開発ではありますが簡単なサンプルであれば動くものができました。
今後はMoqのように処理を置き換えられるのを目標に、もう少しサンプルを拡充してみようと思います。