[英]How to extend class with an extra property
假设我有一个名为Foo
的类。
我无法更改Foo
类,但我不想使用类型为string
名为Bar
的属性来扩展它。
此外,我还有更多像Foo
这样的课程,所以我对“通用”解决方案很感兴趣。
我正在研究ExpandoObject
, dynamic
,它给了我我要求的结果,但我想知道它可以在不使用dynamic
情况下完成......
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;
}
通过使用 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();
}
}
您现在可以看到我们使用接口来扩展我们新创建的类的原因是为了我们可以有一种类型安全的方式来访问它的属性。 如果我们简单地返回一个对象类型,我们将被迫通过反射访问它的属性。
编辑
以下修改版本现在能够实例化位于接口内的复杂类型,并实现其他简单类型。
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();
}
}
由于我的评论变得非常冗长,我想我会添加一个新答案。 这个答案完全是马里奥的工作和思考,只有我的小补充来举例说明我想要表达的内容。
对 mario 的示例进行了一些小的更改,可以使此工作非常好,即,仅更改将现有属性添加为类对象的事实,而不是复制整个类。 无论如何,这就是它的外观(仅添加了修改后的部分,其他所有内容均按照 mario 的回答):
public class Person
{
public int Age { get; set; }
public string FaveQuotation { get; set; }
}
对于IPerson
接口,我们添加实际的Person
类,而不是复制属性:
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; }
}
这转化为更新的用法:
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();
}
希望这对原始 OP 有所帮助,我知道这让我想到,虽然对于Person
类是否应该在方法中与新属性平展到同一级别,陪审团仍然没有定论!! 所以实际上,使用行new DynamicExtension<Person>().ExtendWith<IPerson>();
应该返回一个完全扩展的新对象 - 包括智能。 艰难的呼唤 - 嗯...
在无法访问类定义的情况下,您能做的最好的事情就是创建一个从目标类派生的类。 除非原件是Sealed
。
我知道这会迟到。 已经创建了一个 nuget 包,它抽象了在运行时扩展类型所需的所有复杂性。 它很简单:
var className = "ClassA";
var baseType = typeof(List<string>);
var typeExtender = new TypeExtender(className, baseType);
typeExtender.AddProperty("IsAdded", typeof(bool));
typeExtender.AddProperty<double>("Length");
var returnedClass = typeExtender.FetchType();
var obj = Activator.CreateInstance(returnedClass);
您可以在 repo TypeExtender上找到更多使用说明。 Nuget 包在nuget
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.