简体   繁体   English

如何使用额外的属性扩展类

[英]How to extend class with an extra property

Suppose I've got a class named Foo .假设我有一个名为Foo的类。

I cannot change the Foo class but I wan't to extend it with a property named Bar of type string .我无法更改Foo类,但我不想使用类型为string名为Bar的属性来扩展它。

Also I've got a lot more classes like Foo so I'm interested in a 'generic' solution.此外,我还有更多像Foo这样的课程,所以我对“通用”解决方案很感兴趣。

I'm looking into ExpandoObject , dynamic and it gives me the result I'm asking for but I was wondering it it could be done without using dynamic ...我正在研究ExpandoObjectdynamic ,它给了我我要求的结果,但我想知道它可以在不使用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;
}

Your problem can relatively easily be solved by using Reflection.Emit and run-time code generation.通过使用 Reflection.Emit 和运行时代码生成,您的问题可以相对容易地解决。

Suppose now you have the following class that you would like to extend.假设现在您有以下要扩展的类。

public class Person
{
    public int Age { get; set; }
}

This class represents a person, and contains a property named Age to represent the person's age.这个类代表一个人,并包含一个名为Age的属性来代表这个人的年龄。

In your case, you would also like to add a Name property of type string to represent the person's name.在您的情况下,您还想添加一个字符串类型的Name属性来表示此人的姓名。

The simplest and most streamlined solution would then be to define the following interface.最简单和最精简的解决方案是定义以下接口。

public interface IPerson
{   
    string Name { get; set; }
    int Age { get; set; }
}

This interface, which will be used to extend your class, should contain all the old properties your current class contains, and the new ones you would like to add.这个接口将用于扩展您的类,它应该包含您当前类包含的所有旧属性,以及您想要添加的新属性。 The reason for this will become clear in a moment.其原因将在一瞬间变得清晰。

You can now use the following class definition to actually extend your class by creating a new type at runtime which will also make it derive from the above mentioned interface.您现在可以使用以下类定义通过在运行时创建新类型来实际扩展您的类,这也将使其从上述接口派生。

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());
    }
}

To actually use this class, simply execute the following lines of code.要实际使用此类,只需执行以下代码行即可。

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();
    }
}

You can now see that the reason we used an interface to extend our newly created class is so that we can have a type-safe way of accessing its properties.您现在可以看到我们使用接口来扩展我们新创建的类的原因是为了我们可以有一种类型安全的方式来访问它的属性。 If we simply returned an object type, we would be forced to access its properties by Reflection.如果我们简单地返回一个对象类型,我们将被迫通过反射访问它的属性。

EDIT编辑

The following modified version is now able to instantiate complex types located inside the interface, and implement the other simple ones.以下修改版本现在能够实例化位于接口内的复杂类型,并实现其他简单类型。

The definition of the Person class stays the same, while the IPerson interface now becomes the following. Person 类的定义保持不变,而 IPerson 接口现在变为以下内容。

public interface IPerson
{
    string Name { get; set; }

    Person Person { get; set; }
}

The DynamicExtension class definition now changes to the following. 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;
    }
}

We can now simply execute the following lines of code to get all the appropriate values.我们现在可以简单地执行以下代码行来获取所有适当的值。

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();
    }
}

as my comments were getting very verbose, I thought I'd add a new answer.由于我的评论变得非常冗长,我想我会添加一个新答案。 this answer is completely Mario's work and thinking, only has my minor addition to exemplify what I'm trying to put across.这个答案完全是马里奥的工作和思考,只有我的小补充来举例说明我想要表达的内容。

There are a few minor changes to mario's example that would make this work very well, namely, just changing the fact that the existing properties are added as the class object, rather than duplicating the entire class.对 mario 的示例进行了一些小的更改,可以使此工作非常好,即,仅更改将现有属性添加为类对象的事实,而不是复制整个类。 Anyway, here's how this looks (only amended sections added, all else remains as per mario's answer):无论如何,这就是它的外观(仅添加了修改后的部分,其他所有内容均按照 mario 的回答):

public class Person
{
    public int Age { get; set; }
    public string FaveQuotation { get; set; }
}

for the IPerson interface, we add the actual Person class, rather than copying the properties:对于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; }
}

This translates to an updated usage of:这转化为更新的用法:

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();
}

Hope this helps the original OP, I know it made me think, tho the jury is still out as to whether the Person class should be flattened to the same level in the method as the new properties!!希望这对原始 OP 有所帮助,我知道这让我想到,虽然对于Person类是否应该在方法中与新属性平展到同一级别,陪审团仍然没有定论!! So in effect, using the line new DynamicExtension<Person>().ExtendWith<IPerson>();所以实际上,使用行new DynamicExtension<Person>().ExtendWith<IPerson>(); SHOULD return a fully extended new object -intellisence included.应该返回一个完全扩展的新对象 - 包括智能。 Tough call - hmmm...艰难的呼唤 - 嗯...

Without having access to the class definition, the best you could do is create a class which is derived from the target class.在无法访问类定义的情况下,您能做的最好的事情就是创建一个从目标类派生的类。 Unless the original is Sealed .除非原件是Sealed

I know this is coming late.我知道这会迟到。 A nuget package that abstracts all the complexity required to extend a type at runtime has been created.已经创建了一个 nuget 包,它抽象了在运行时扩展类型所需的所有复杂性。 It is as simple as:它很简单:

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);

You can find more usage instructions on the repo TypeExtender .您可以在 repo TypeExtender上找到更多使用说明。 Nuget package is at nuget Nuget 包在nuget

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM