この問題は、Reflection.Emit とランタイム コード生成を使用することで比較的簡単に解決できます。
拡張したい次のクラスがあるとします。
public class Person
{
public int Age { get; set; }
}
このクラスは人物を表し、その人物の年齢を表す Ageという名前のプロパティを含みます。
あなたの場合、文字列型のNameプロパティを追加して、人の名前を表すこともできます。
最も単純で合理的な解決策は、次のインターフェイスを定義することです。
public interface IPerson
{
string Name { get; set; }
int Age { get; set; }
}
クラスを拡張するために使用されるこのインターフェイスには、現在のクラスに含まれるすべての古いプロパティと、追加する新しいプロパティが含まれている必要があります。この理由はすぐに明らかになります。
次のクラス定義を使用して、実行時に新しい型を作成することで実際にクラスを拡張できます。これにより、上記のインターフェイスから派生させることもできます。
class DynamicExtension<T>
{
public K ExtendWith<K>()
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("Module");
var type = module.DefineType("Class", TypeAttributes.Public, typeof(T));
type.AddInterfaceImplementation(typeof(K));
foreach (var v in typeof(K).GetProperties())
{
var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });
var getGenerator = getter.GetILGenerator();
var setGenerator = setter.GetILGenerator();
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Ldfld, field);
getGenerator.Emit(OpCodes.Ret);
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldarg_1);
setGenerator.Emit(OpCodes.Stfld, field);
setGenerator.Emit(OpCodes.Ret);
property.SetGetMethod(getter);
property.SetSetMethod(setter);
type.DefineMethodOverride(getter, v.GetGetMethod());
type.DefineMethodOverride(setter, v.GetSetMethod());
}
return (K)Activator.CreateInstance(type.CreateType());
}
}
このクラスを実際に使用するには、次のコード行を実行するだけです。
class Program
{
static void Main(string[] args)
{
var extended = new DynamicExtension<Person>().ExtendWith<IPerson>();
extended.Age = 25;
extended.Name = "Billy";
Console.WriteLine(extended.Name + " is " + extended.Age);
Console.Read();
}
}
新しく作成したクラスを拡張するためにインターフェイスを使用した理由は、そのプロパティにタイプ セーフな方法でアクセスできるようにするためであることがわかります。単純にオブジェクト タイプを返すと、Reflection によってそのプロパティにアクセスする必要があります。
編集
次の変更されたバージョンでは、インターフェイス内にある複雑な型をインスタンス化し、他の単純なものを実装できるようになりました。
Person クラスの定義は同じままですが、IPerson インターフェイスは次のようになりました。
public interface IPerson
{
string Name { get; set; }
Person Person { get; set; }
}
DynamicExtension クラスの定義は、次のように変更されます。
class DynamicExtension<T>
{
public T Extend()
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("Module");
var type = module.DefineType("Class", TypeAttributes.Public);
type.AddInterfaceImplementation(typeof(T));
foreach (var v in typeof(T).GetProperties())
{
var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });
var getGenerator = getter.GetILGenerator();
var setGenerator = setter.GetILGenerator();
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Ldfld, field);
getGenerator.Emit(OpCodes.Ret);
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldarg_1);
setGenerator.Emit(OpCodes.Stfld, field);
setGenerator.Emit(OpCodes.Ret);
property.SetGetMethod(getter);
property.SetSetMethod(setter);
type.DefineMethodOverride(getter, v.GetGetMethod());
type.DefineMethodOverride(setter, v.GetSetMethod());
}
var instance = (T)Activator.CreateInstance(type.CreateType());
foreach (var v in typeof(T).GetProperties().Where(x => x.PropertyType.GetConstructor(new Type[0]) != null))
{
instance.GetType()
.GetProperty(v.Name)
.SetValue(instance, Activator.CreateInstance(v.PropertyType), null);
}
return instance;
}
}
次のコード行を実行するだけで、適切な値をすべて取得できます。
class Program
{
static void Main(string[] args)
{
var extended = new DynamicExtension<IPerson>().Extend();
extended.Person.Age = 25;
extended.Name = "Billy";
Console.WriteLine(extended.Name + " is " + extended.Person.Age);
Console.Read();
}
}