[英]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.