How to extend class with an extra property
假设我有一个名为
我不能更改
另外,我还有很多类,比如
我正在研究
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | static void Main(string[] args) { var foo = new Foo() { Thing ="this" }; var fooplus = Merge(foo, new { Bar =" and that" }); Console.Write(string.Concat(fooplus.Thing, fooplus.Bar)); Console.ReadKey(); } public class Foo { public string Thing { get; set; } } public static dynamic Merge(object item1, object item2) { if (item1 == null || item2 == null) return item1 ?? item2 ?? new ExpandoObject(); dynamic expando = new ExpandoObject(); var result = expando as IDictionary<string, object>; foreach (System.Reflection.PropertyInfo fi in item1.GetType().GetProperties()) { result[fi.Name] = fi.GetValue(item1, null); } foreach (System.Reflection.PropertyInfo fi in item2.GetType().GetProperties()) { result[fi.Name] = fi.GetValue(item2, null); } return result; } |
使用反射、发出和运行时代码生成可以相对容易地解决您的问题。
假设现在您有了下面要扩展的类。
1 2 3 4 | public class Person { public int Age { get; set; } } |
这个类表示一个人,并且包含一个名为Age的属性来表示这个人的年龄。
在您的例子中,您还希望添加一个字符串类型的name属性来表示人员的姓名。
最简单和最精简的解决方案将是定义以下接口。
1 2 3 4 5 | public interface IPerson { string Name { get; set; } int Age { get; set; } } |
此接口将用于扩展类,它应包含当前类包含的所有旧属性以及要添加的新属性。这一点的原因很快就会清楚。
现在,您可以使用以下类定义通过在运行时创建一个新类型来实际扩展类,该类型还将使它从上述接口派生。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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()); } } |
要实际使用这个类,只需执行以下代码行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); } } |
现在您可以看到,我们使用接口来扩展新创建的类的原因是,我们可以使用一种类型安全的方法来访问它的属性。如果我们只是返回一个对象类型,我们将被迫通过反射访问它的属性。
编辑
下面的修改版本现在能够实例化位于接口内部的复杂类型,并实现其他简单类型。
Person类的定义保持不变,而iperson接口现在变为以下内容。
1 2 3 4 5 6 | public interface IPerson { string Name { get; set; } Person Person { get; set; } } |
现在,dynamicextension类定义更改为以下内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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; } } |
我们现在可以简单地执行下面的代码行来获取所有适当的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); } } |
由于我的评论越来越冗长,我想我应该添加一个新的答案。这个答案完全是马里奥的工作和思考,只有我的小补充来举例说明我想表达的东西。
对Mario的示例进行了一些微小的更改,这将使此工作非常顺利,也就是说,只是更改了将现有属性添加为类对象的事实,而不是复制整个类。不管怎样,这里是这样看的(只增加了修改的部分,其余的都按照马里奥的回答):
1 2 3 4 5 | public class Person { public int Age { get; set; } public string FaveQuotation { get; set; } } |
对于
1 2 3 4 5 6 7 | public interface IPerson { // extended property(s) string Name { get; set; } // base class to extend - tho we should try to overcome using this Person Person { get; set; } } |
这转化为更新的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | static void Main(string[] args) { var extended = new DynamicExtension<Person>().ExtendWith<IPerson>(); var pocoPerson = new Person { Age = 25, FaveQuotation ="2B or not 2B, that is the pencil" }; // the end game would be to be able to say: // extended.Age = 25; extended.FaveQuotation ="etc"; // rather than using the Person object along the lines below extended.Person = pocoPerson; extended.Name ="Billy"; Console.WriteLine(extended.Name +" is" + extended.Person.Age +" loves to say: '" + extended.Person.FaveQuotation +"'"); Console.ReadKey(); } |
希望这有助于最初的操作,我知道这让我想,但对于
在没有访问类定义的情况下,最好创建一个从目标类派生的类。除非正本已盖章。