2009-08-27 67 views
12

我讨厌有一堆“左/右”的方法。每次添加或删除一个属性时,我都必须修复每种方法。代码本身看起来......错了。如何使用反射来简化构造函数和比较?

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

真的,这只是一个展开循环,遍历属性,在对象之间复制它们。那么为什么不诚实说这个事实呢?反思救援!

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

我可能只是试图迫使范式我从Lua了解到在C#中,但这个特殊的例子似乎并不嫌臭给我。从这里开始,我开始做一些对田地秩序敏感的更复杂的事情。例如,而不是具有实质上等同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"), 
}; 

所以现在我有我想要的iterability,但我还是有堆栈的代码镜像我感兴趣的每个属性(我也为CompareTo执行此操作,使用不同顺序的一组不同的属性)。更糟糕的是,这是失去了强大的打字。这真的开始闻起来。

那么在每个属性上使用属性来定义顺序呢?我开始走下这条路,事实上它运行良好,但它使整个事情看起来臃肿。它在语义上效果很好,但我总是谨慎使用高级功能,因为它们“整洁”。以这种方式使用反射矫枉过正?是否有其他解决方案来解决我缺少的左/右代码问题?

+0

i'dd添加语言的标签,以获得更多的观众 – Toad 2009-08-27 14:07:13

+0

记住,反思总是在C#中的性能问题。你多长时间一次操作Foos?混合解决方案可以使用Reflection.Emit,并缓存结果代码 - 启动速度较慢,执行速度较快。 – zildjohn01 2009-08-27 14:21:40

+1

C#4.0带有内置的编译器,可以帮助生成代码并使用它,现在我们也有类似的问题,但是我们使用XML Class结构设计了解决方案,并且我们使用自己的代码生成方法来构建类生成很多依赖于成员名称的其他方法,它基本上是一个ORML,但是一切都是自动的。 – 2009-08-27 15:01:08

回答

5

使用反射本身并不坏,但是如果以递归方式进行操作,性能会受到影响。

我不是硬编码拷贝构造函数的粉丝,因为开发人员在向类中添加新属性时会忘记更新它们。

有完成你想要的东西,包括马克·Gravells Hyper Property Descriptor的其他方式,或者如果您想了解一些IL和操作码,您可以使用System.Reflection.Emit甚至Cecil from Mono

这里,你都不可能满足你的需要使用Hyper属性描述符的例子:

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

如果您决定进行使用反射,可以减少通过缓存调用的GetProperties击中性能( )像这样:

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

Type.GetProperties已经做了自己的缓存(第一次调用和后续调用的时间)。反射的真正性能打击是属性。SetValue调用,你可以用MSIL代来缓解等 – 2009-08-27 14:34:38

+0

@Robon Fonseca-Ensor谢谢,我不知道。 – grenade 2009-08-27 14:36:54

+0

嗯......如果性能问题是在设置值,那么这可能不是要走的路。试图处理所有这些低级别的东西肯定会过度杀伤。感谢您的意见。 – Cogwheel 2009-08-27 15:10:32

2

恕我直言,反思是C#的一个非常强大的功能,但是这很可能导致臃肿的代码,这增加了多少代码的学习曲线,减少了维护。你会更容易犯错误(一旦基本重构可能导致错误),更害怕改变任何财产的名称(如果你碰巧找到一个更好的名字)或类似的东西。

我个人有一个类似问题的代码,我也有同样的想法来添加属性来维护秩序等等。但是我的团队(包括我)认为最好不要浪费一些时间来改变设计而不需要这个。也许这个问题是由不好的设计造成的(好吧,这是我的情况,但我不能说出你的相同)。

+0

我不知道在更改属性时它是如何更容易出错的,因为整个过程不需要直接在代码中引用属性名称。 现在我只是建立最终将被映射到数据库中的域模型,所以添加/删除/重命名属性将成为发展(尤其是因为我想了TDD首次)期间几乎不停 – Cogwheel 2009-08-27 14:54:26

+0

你有“typeof运算(的IFoo).GetProperty(”酒吧“),”在你的代码,它引用属性名称直接 – 2009-08-27 14:57:47

+0

因此最后两段在我的问题;) – Cogwheel 2009-08-27 15:04:40

3

我知道这个问题已经有了答案,但我想指出的是,有一个图书馆结合了少数人讨论过的针对性能影响的一些缓解策略。

该库名为AutoMapper,它从一个对象映射到另一个对象,并通过动态创建一个IL组合来实现。这保证了比第一次打其他,你会得到优异的性能和你的代码就会简单得多:

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

这往往工作伟大和具有不被在这里发明的额外的奖金,这我是的爱好者。

我做了一些性能测试,并在20毫秒(仍然非常快)的第一次命中后,它接近于0,你可以得到。相当不俗。

希望这可以帮助别人。

+0

或者你可以使用emitmapper这是速度更快。 – 2013-08-19 13:14:44

+0

我不认为emitmapper在09年是可利用的,当我提出这个:) – 2013-08-20 02:47:52

+0

嗯......这是从来不迟到,以提高你的答案:) – 2013-08-21 06:32:07

2

基本问题是你正在尝试使用静态类型的语言,如动态类型的语言。

实际上并不需要任何幻想。如果您想要迭代属性,可以使用Map <>作为班级中所有属性的后备存储。

巧合的是,这正是VS项目向导如何为您呈现应用程序设置。 (见System.Configuration.ApplicationSettingsBase)它也很“LUA样”

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

不错!非常有趣的策略。这将允许您遍历后备存储并以非常简单的循环复制所有内容。非常有创意。 – 2009-09-02 04:45:29

+0

感谢您的提示。我可能最终会在某个时候使用它,但它不适合当前的任务。 – Cogwheel 2009-09-02 16:18:41

相关问题