如何使用额外属性扩展类

时间:2013-11-01 16:55:39

标签: c# dynamic reflection

假设我有一个名为Foo的类。

我无法更改Foo类,但我不想使用类型为Bar的{​​{1}}属性扩展它。

此外,我还有更多类string,所以我对'通用'解决方案感兴趣。

我正在调查FooExpandoObject它给了我我要求的结果,但我想知道它可以在不使用dynamic的情况下完成... < / p>

dynamic

4 个答案:

答案 0 :(得分:11)

使用Reflection.Emit和运行时代码生成可以相对轻松地解决您的问题。

假设您现在有以下要扩展的类。

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

此类代表一个人,并包含一个名为年龄的属性来表示此人的年龄。

在您的情况下,您还要添加 string 类型的名称属性来表示此人的姓名。

最简单,最简化的解决方案是定义以下界面。

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

答案 1 :(得分:3)

由于我的评论变得非常冗长,我想我会添加一个新答案。这个答案完全是马里奥的工作和思考,只有我的一小部分内容才能体现我想要实现的目标。

马里奥的例子有一些细微的变化可以使这项工作非常好,即只是改变现有属性作为类对象添加的事实,而不是复制整个类。无论如何,这是看起来如何(只修改了部分,其他所有其他内容仍按照马里奥的回答):

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>();应该返回一个完全扩展的新对象 - 包含的内容。艰难的电话 - 嗯......

答案 2 :(得分:1)

无法访问类定义,您可以做的最好的事情是创建一个派生自目标类的类。除非原件是密封的。

答案 3 :(得分:0)

我知道这来晚了。已经创建了一个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);

您可以在仓库TypeExtender中找到更多使用说明。 Nuget软件包位于nuget