简体   繁体   English

如何使用反射来简化构造函数和比较?

[英]How to use reflection to simplify constructors & comparisons?

I hate having a bunch of "left/right" methods. 我讨厌拥有一堆“左/右”方法。 Every time a property is added or removed, I have to fix up each method. 每次添加或删除属性时,我都必须修复每个方法。 And the code itself just looks ... wrong. 而代码本身看起来......错了。

public Foo(Foo other)
{
    this.Bar = other.Bar;
    this.Baz = other.Baz;
    this.Lur = other.Lur;
    this.Qux = other.Qux;
    this.Xyzzy= other.Xyzzy;
}

Really this is just an unrolled loop that iterates through the properties, copying them between objects. 实际上,这只是一个展开的循环,它遍历属性,在对象之间复制它们。 So why not be honest about that fact? 那么为什么不对这个事实说实话呢? Reflection to the rescue! 反思救援!

public Foo(IFoo other)
{
    foreach (var property in typeof(IFoo).GetProperties())
    {
        property.SetValue(this, property.GetValue(other, null), null);
    }
}

I may just be trying to force a paradigm I learned from Lua onto C#, but this particular example doesn't seem too smelly to me. 我可能只是试图强迫我从Lua学习到C#的范例,但这个特殊的例子对我来说似乎并不太臭。 From here, I started to do some more complex things that were sensitive to the order of the fields. 从这里开始,我开始做一些对字段顺序敏感的更复杂的事情。 For example, rather than having a stack of virtually identical if statements to compose a string from the fields, I just iterate over them in the desired order: 例如,我只是按照所需的顺序迭代它们,而不是使用几乎相同的if语句堆栈来组成字段中的字符串:

public override string ToString()
{
    var toJoin = new List<string>();
    foreach (var property in tostringFields)
    {
        object value = property.GetValue(this, null);
        if (value != null)
            toJoin.Add(value.ToString());
    }
    return string.Join(" ", toJoin.ToArray());
}
private static readonly PropertyInfo[] tostringFields =
{
    typeof(IFoo).GetProperty("Bar"),
    typeof(IFoo).GetProperty("Baz"),
    typeof(IFoo).GetProperty("Lur"),
    typeof(IFoo).GetProperty("Qux"),
    typeof(IFoo).GetProperty("Xyzzy"),
};

So now I have the iterability I wanted, but I still have stacks of code mirroring each property I'm interested in (I'm also doing this for CompareTo, using a different set of properties in a different order). 所以现在我有了我想要的可迭代性,但我仍然有一堆代码镜像我感兴趣的每个属性(我也是为CompareTo做这个,使用不同的属性以不同的顺序)。 Worse than that is the loss of strong typing. 更糟糕的是失去了强烈的打字。 This is really starting to smell. 这真的开始闻起来了。

Well what about using attributes on each property to define the order? 那么在每个属性上使用属性来定义顺序呢? I started down this road and indeed it worked well, but it just made the whole thing look bloated. 我开始走这条路,确实运作良好,但它让整个事情变得臃肿。 It works great semantically, but I'm always wary of using advanced features just because they're "neat." 它在语义上很有效,但我总是担心使用高级功能只是因为它们“整洁”。 Is using reflection in this way overkill? 以这种方式使用反射是否过度杀伤? Is there some other solution to the left/right code problem I'm missing? 是否还有其他解决左/右代码问题的解决方案?

Using reflection in and of itself is not bad, but you will take a performance hit especially if you do it recursively. 使用反射本身并不坏,但是如果你以递归方式执行它,你会受到性能影响。

I am not a fan of the hard coded copy constructors either because developers forget to update them when they add new properties to a class. 我不是硬编码复制构造函数的粉丝,因为开发人员在向类添加新属性时忘记更新它们。

There are other ways of accomplishing what you want, including Marc Gravells Hyper Property Descriptor or if you want to learn some IL and OPCodes you can use System.Reflection.Emit or even Cecil from Mono . 还有其他方法可以实现您想要的功能,包括Marc Gravells 超级属性描述符,或者如果您想学习一些IL和OPCode,您可以使用System.Reflection.Emit甚至是Mono的Cecil

Here's an example of using Hyper Property Descriptor that you can possibly tailor to your needs: 以下是使用超级属性描述符的示例,您可以根据自己的需要进行定制:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Hyper.ComponentModel;
namespace Test {
    class Person {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class Program {
        static void Main() {
            HyperTypeDescriptionProvider.Add(typeof(Person));
            var properties = new Dictionary<string, object> { { "Id", 10 }, { "Name", "Fred Flintstone" } };
            Person person = new Person();
            DynamicUpdate(person, properties);
            Console.WriteLine("Id: {0}; Name: {1}", person.Id, person.Name);
            Console.ReadKey();
        }
        public static void DynamicUpdate<T>(T entity, Dictionary<string, object>  {
            foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(typeof(T)))
                if (properties.ContainsKey(propertyDescriptor.Name))
                    propertyDescriptor.SetValue(entity, properties[propertyDescriptor.Name]);
        }
    }
}

If you decide to carry on using reflection, you can reduce the performance hit by caching your calls to GetProperties() like so: 如果您决定继续使用反射,则可以通过缓存对GetProperties()的调用来降低性能:

public Foo(IFoo other) {
    foreach (var property in MyCacheProvider.GetProperties<IFoo>())
        property.SetValue(this, property.GetValue(other, null), null);
}

I know there is already an answer to this, but I wanted to point out that there is a library that combines a few of the mitigation strategies for the performance impact that a few people have discussed. 我知道已经有了答案,但我想指出,有一个库结合了一些缓解策略来解决一些人讨论过的性能影响。

The library is called AutoMapper and it maps from one object to another and does so by dynamically creating an IL assembly on the fly. 该库称为AutoMapper ,它从一个对象映射到另一个对象,并通过动态创建IL程序集来实现。 This ensures that other than a first time hit, you get superior performance and your code would be much simpler: 这确保了除了第一次点击之外,您将获得卓越的性能,并且您的代码将更加简单:

public Foo(Foo other)
{
    Mapper.Map(other, this);
}

This tends to work great and has the added bonus of not being invented here, which I'm a fan of. 这往往很有效,并且还有一个额外的好处,就是不会在这里被发明,我是其中的粉丝。

I did some performance testing and after the first hit of 20 ms (still pretty fast) it was about as close to 0 as you can get. 我做了一些性能测试,在第一次达到20毫秒后(仍然相当快),它几乎接近0,你可以得到。 Pretty impressive. 令人印象深刻

Hope this helps someone. 希望这有助于某人。

IMHO, reflection is a very powerful feature of C#, but which is very likely to result in a bloated code, and which adds much to the learning curve of the code and reduces maintainability. 恕我直言,反射是C#的一个非常强大的功能,但很可能会导致代码膨胀,并且会增加代码的学习曲线并降低可维护性。 You'll be more likely to commit mistakes (once basic refactoring may lead to errors), and more afraid to change the name of any property (if you happen to find a better name) or stuff like that. 您将更有可能犯错(一旦基本重构可能导致错误),并且更害怕更改任何属性的名称(如果您碰巧找到更好的名称)或类似的东西。

I personally have a code with a similar problem, and I had the same idea of adding attributes to maintain order and etc. But my team (including me) thought it was better to lose some time changing the design not to need this. 我个人有一个类似问题的代码,我有同样的想法添加属性来维护秩序等等。但我的团队(包括我)认为最好不要浪费时间改变设计而不需要这个。 Perhaps this problem is caused by bad design (well, it was in my case, but I can't tell the same about yours). 也许这个问题是由糟糕的设计造成的(嗯,这是我的情况,但我不能说你的相同)。

The basic problem is you are trying to use a statically typed language like a dynamically typed one. 基本问题是您尝试使用静态类型语言,如动态类型语言。

There isn't really a need for anything fancy. 没有任何想象力的需要。 If you want to be able to iterate the properties, you can use a Map<> as the backing store for all the properties in your class. 如果希望能够迭代属性,可以使用Map <>作为类中所有属性的后备存储。

Coincidentally this is exactly how the VS project wizard implmements application settings for you. 巧合的是,VS项目向导正是如何为您设计应用程序设置的。 (see System.Configuration.ApplicationSettingsBase) Its also very 'lua-like' (请参阅System.Configuration.ApplicationSettingsBase)它也非常'lua-like'

   public bool ConfirmSync {
        get {
            return ((bool)(this["ConfirmSync"]));
        }
        set {
            this["ConfirmSync"] = value;
        }
    }

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

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