关于c#:如何使用额外的属性扩展类

How to extend class with an extra property

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

我不能更改Foo类,但我不想用string类型的名为Bar的属性来扩展它。

另外,我还有很多类,比如Foo,所以我对"通用"解决方案感兴趣。

我正在研究ExpandoObjectdynamic,它给出了我想要的结果,但我想知道它可以在不使用dynamic的情况下完成。

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

  • 为什么不使用扩展方法?
  • 部分课程?
  • stackoverflow.com/a/619047/598289您不能直接在c_中这样做,但这里有一个页面声称使用反射代码project.com/articles/386911/&hellip进行解决方案;
  • @Liam,我假设"我不能改变foo类"也意味着他不能将其标记为partial
  • 如果您可以将类的签名更改为包含partial,那么这似乎是一种很好的方法,或者简单地将extend与子类一起更改。
  • 你为什么要这么做?向类中添加属性不是您在C中可以做的事情,而且您可以用比使用dynamic更好的方法来完成它。
  • decorator模式的实现在这里可能很有用,但这意味着需要对代码进行一些重构以使用更多的抽象。
  • 如果所需的属性是常量,则可以使用自定义属性。
  • 我将在这里使用适配器模式,并使用composition"继承"状态/行为:oodesign.com/adapter-pattern.html
  • 如果Foo未密封,可以延长,如public class FooBar : Foo { public string Bar { get; set; } }
  • 伙计们,我有很多类,如果我不需要为每个类都有一个分部或者使用继承来扩展它,那就太好了。
  • 好吧,我想那时你必须使用你目前的方法。但是要注意性能的影响。
  • 您提到您有很多类,但是,您没有提到的是下游维护需求。你要么现在就投资基金会,忍受痛苦,要么在6个月后永远回头找那个愤怒的支持程序员——他们知道你住在哪里!!)
  • …所以我要说的是,看看更传统的继承,而不是蓝时期的方法
  • 最后一点注意——当然,您可以采用另一种方法,研究使用automapper,并在目标上有一组派生类,然后简单地映射出适合您的用例的任何方向。
  • @Jimtollan下游是通过webapi的json
  • @Ralfdekleine您的类有类似于和接口的任何公共内容。这要求接口上有一个扩展方法。
  • @JA72说我会有一个接口,你能演示一下如何使用扩展方法吗?
  • @Redwan自定义属性如何帮助我?我需要额外的财产。


使用反射、发出和运行时代码生成可以相对容易地解决您的问题。

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

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

  • 这段代码让我很兴奋,唯一的缺点是我需要一个与人和我需要的额外属性相匹配的接口。但我可能会让它工作。有趣。
  • +这看起来很有趣,暗示了一些不错的替代用途。我非常喜欢它。如果您能够以类似于OP合并的方式重构它(即不需要在接口中复制基本属性),那么在许多应用程序中这将非常有用。甚至我自己也可能在那个重构中狂欢…
  • 啊,最后的结果是艾伯森延长了!嗯!
  • @Ralfdekleine我可以编辑我的答案以更好地满足您的需求。你到底想让我改变什么?D
  • @吉米托兰完全正确。这就是为什么我首先使用接口的原因。这样我们最终得到了一个强类型的结果,它的属性可以以一种类型安全的方式访问,并且可以使用IntelliSense可见。
  • 马里奥-这一直在我的脑海中运行,发生了一些事情。是否可以定义以下内容:public class Person { public int Age { get; set; } } public interface IPerson { string Name { get; set; } Person Person { get; set; } },即重构方法以接受完整的类对象作为属性,而不是复制属性。然后,您可以在代码中将其称为:extended.Person.Age = 25;。显然,目前这不起作用,但我相信这是可以调整的。去争取:
  • 空间不足。显然,在您的DynamicExtension中要解决的剩余问题是创建一个类型为'T'的新类,以满足在类型级别传递的类,而不是单个属性级别传递的类。当然,这可以在设置属性时创建,即extended.Person = new Person();,但这有点不合理,也不是特别合乎逻辑。把这个挑战扔给任何读过这个的人!
  • 吉姆,我已经更新了我的答案以符合你的想法。您能详细说明一下您希望我如何处理扩展方法,以及您认为它应该如何接受对象而不是类型吗?
  • 嘿,马里奥-干得好。好吧,在完美的世界里,我很想看看你的代码(不要问我怎么做),以IPerson的一部分的Person类型为例,然后当它被扩展时,返回一个全新的混合类型,其中Person属性被压平到与新IPerson属性相同的水平。基本上,您现在有了一个合并属性的新对象。我也理解在设计阶段用IntelliSense实现这一点的复杂性!但这将是一个很好的工程
  • 但要补充的是,这是你最初答案的一个很好的进展,它很好地把所有的东西都包起来了。
  • 实际上,最完美的最终版本将被实例化为var extended = new DynamicExtension().ExtendWith();。在我们的IPerson接口中,我们只有新的属性(没有提到Person)。然后,此调用将把PersonIPerson中的属性合并为单个类型。这意味着您可以将IPerson应用于各种其他类型,如var extended = new DynamicExtension().ExtendWith();等。正如我所说,我明白这几乎是不可能的!!)
  • 吉姆,我当然可以把这两种类型合并成一种新的类型。但将返回的结果类型将是Object类型,因此没有IntelliSense。为了让我们使用IntelliSense,我们只需要预先设置类型或其接口。
  • 马里奥-是的,这太真实了。我认为您的更新实现最接近于确定OP的需求。我个人还没有这个的用例,但它确实吸引了我的想象力。感谢所有的投入-希望我能投票不止一次!!:-)现在一切都很好……


由于我的评论越来越冗长,我想我应该添加一个新的答案。这个答案完全是马里奥的工作和思考,只有我的小补充来举例说明我想表达的东西。

对Mario的示例进行了一些微小的更改,这将使此工作非常顺利,也就是说,只是更改了将现有属性添加为类对象的事实,而不是复制整个类。不管怎样,这里是这样看的(只增加了修改的部分,其余的都按照马里奥的回答):

1
2
3
4
5
public class Person
{
    public int Age { get; set; }
    public string FaveQuotation { get; set; }
}

对于IPerson接口,我们添加实际的Person类,而不是复制属性:

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

希望这有助于最初的操作,我知道这让我想,但对于Person类是否应该在方法中被扁平到与新属性相同的级别,陪审团还没有定论!!因此,实际上,使用new DynamicExtension().ExtendWith();行应该返回一个完全扩展的新对象-包括intellisence。艰难的召唤-嗯…


在没有访问类定义的情况下,最好创建一个从目标类派生的类。除非正本已盖章。

  • @Sjuan76好点,把它拆了。