2009-08-27 68 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

相關問題