2012-12-23 22 views
7

首先我們可能都同意最好的方法是在自定義對象/實體內部實現複製功能。但請考慮這種情況。我們沒有這個選項,我們也不想編寫特定的函數來完成實體的複製,因爲實體將來會被更改,所以我們的複製函數會失敗。複製不支持複製功能的動態對象的最快方法

這裏被簡化當前實體的版本:

[Serializable] 
class MyEntity 
{ 
    public MyEntity() 
    { 
    } 

    public MyEntity(int id, string name) 
    { 
     this.Id = id; 
     this.Name = name; 
    } 

    public int Id { get; set; } 

    public string Name { get; set; } 

    public MyEntity Copy() 
    { 
     throw new NotImplementedException(); 
    } 
} 

覆蓋所有上述要求,我想出了兩個解決方案:

 //original... 
     MyEntity original = new MyEntity() { Id = 1, Name = "demo1" }; 

     //first way to copy object... 
     List<MyEntity> list = new List<MyEntity>() { original}; 
     MyEntity copy1 = list.ConvertAll(entity => new MyEntity(entity.Id, entity.Name))[0]; 

     //second way to copy object... 
     byte[] bytes = SerializeEntity(original); 
     MyEntity copy2 = (MyEntity)DeserializeData(bytes); 


    byte[] SerializeEntity(object data) 
    { 
     byte[] result = null; 
     using (MemoryStream ms = new MemoryStream()) 
     { 
      BinaryFormatter formatter = new BinaryFormatter(); 
      formatter.Serialize(ms, data); 
      result = ms.ToArray(); 
     } 
     return result; 
    } 

    object DeserializeData(byte[] data) 
    { 
     object result = null; 
     using(MemoryStream ms = new MemoryStream(data)) 
     { 
      BinaryFormatter formatter = new BinaryFormatter(); 
      result = formatter.Deserialize(ms); 
     } 
     return result; 
    } 

現在的問題。背後的最佳解決方案是什麼,以及爲什麼,第一或第二?考慮到上述要求,是否有更好的方法來完成複製?複印件將大量完成。

PS note: 我知道第一種方法基本上已經是Honza指出的複製功能。作爲自定義複製功能,我有點像序列化和接近快速的多功能。

回答

1

你可以嘗試使用AutoMapper

Mapper.CreateMap<MyEntity, MyEntity>(); 

... 

var copy3 = Mapper.Map<MyEntity, MyEntity>(original); 
+0

我已經閱讀了幾篇關於AutoMapper的文章,但無法對我的例子中的性能做出任何真正的結論。有些人聲稱這不是所有情況下的最佳解決方案。您能否使用AutoMapper建議任何可能導致性能不佳的指針?此時選擇+1 –

7

首先,我們大概都同意最好的方法是在自定義對象/實體內部實現複製功能。

我不同意。我討厭每次寫這樣的方法。這裏是我的建議使用擴展方法:

public static T Copy<T>(this T obj) 
    where T : class 
{ 
    using (MemoryStream stream = new MemoryStream()) 
    { 
     BinaryFormatter formatter = new BinaryFormatter(); 
     formatter.Serialize(stream, obj); 

     stream.Seek(0, SeekOrigin.Begin); 
     return formatter.Deserialize(stream) as T; 
    } 
} 

這基本上是你的第二個解決方案,但稍有優化。不需要將MemoryStream複製到一個字節數組,然後從中創建另一個MemoryStream。

最好的事情是它是通用的,可以用於具有[Serializable]屬性的每個對象。我相當肯定它比你的第一個解決方案更快,你必須訪問每個屬性(雖然我沒有測量)。

編輯:

好吧,其實我做了一些測量了。我對演出的第一個假設是完全錯誤的!

我創建了隨機值百萬myEntity所的對象,然後複製它們(我也算Honza Brestan的深與淺拷貝提示):

深拷貝與二進制格式:14.727小號
深備份複製方法:0.490小號
淺拷貝與反思:5.499小號
淺拷貝與複製方法:0.144小號

+0

這類似於我們使用的東西,但是,它可能是值得注意的是,不同的「T」提供的每一次新的方法必須是由編譯器生成。這會導致輕微的CPU開銷和更多的內存使用。 – MadSkunk

+0

@pescolino使用序列化確實是如果對象被標記爲可序列化的最有效的證明,但它也是最慢的。我真的想要獲得一些聰明和快速的方法來做副本,但我知道沒有「免費午餐」。你總是失去一些東西來獲得其他的東西。 –

+0

@GregorPrimar閱讀[提高性能反思,我應該考慮哪些替代方法](http://stackoverflow.com/questions/1027980/improving-performance-reflection-what-al- ors-應該考慮),尤其是[Jon Skeet的博客](http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx),正如他在答案中提到的那樣。 – pescolino

1

是你的第一次嘗試寫自己的複製方法之間的區別?

public MyEntity Copy() 
{ 
    return new MyEntity(this.Id, this.Name); 
} 

對我來說,這看起來比您的收藏嘗試,它不完全一樣的反正更好 - 在這兩種情況下,你必須明確地命名的所有屬性。

如果您不能修改實體類本身,你仍然可以創建一個擴展方法(放置在一個靜態類從您要使用的複製邏輯可見)

public static MyEntity Copy(this MyEntity source) 
{ 
    return new MyEntity(source.Id, source.Name); 
} 

至於第二次嘗試,你有沒有考慮過兩者之間的差異?他們不完全一樣。第一個創建一個副本,而第二個(提供整個對象樹是可序列化的)產生一個副本。不同的是它的屬性是否被複制,或者原始對象和它的拷貝都引用了相同的對象。這同樣適用於pescolino的版本,它看起來非常漂亮。

所以問題是你想要/需要哪個副本。

對於一個真正的動態(但可能不是很有效)複製方法我認爲你需要使用反射,枚舉所有的屬性,並將它們的值從原始對象複製到副本。非完整的演示版本可能是這樣的:

public static MyEntity Copy(this MyEntity source) 
{ 
    var result = new MyEntity(); 

    var properties = source.GetType().GetProperties(
      BindingFlags.Instance | BindingFlags.Public); 

    foreach (var property in properties) 
    { 
     var val = property.GetValue(source, null); 
     property.SetValue(result, val, null); 
    } 

    return result; 
} 

這種方法有其自身的,即性能,偶爾需要處理特殊情況(索引,非公共屬性...)的問題,但將獲得完成了工作,並且也用於不可序列化的對象。通用版本也很容易實現 - 無論您需要,這都取決於您。

同樣值得注意的是,因爲我和pescolino都提出使用擴展方法,所以他們可能存在一個問題。如果您的實體真的包含與擴展名相同的簽名的Copy方法,編譯器將決定使用它而不是擴展名。被調用時顯然會拋出NotImplementedException。所以如果是這種情況(這不僅僅是你的示例代碼),它可能是一個嚴重的「陷阱」。在這種情況下唯一的解決方案是更改擴展方法的簽名,最好是更改其名稱。

+0

我同意你的看法,這不是一個很大的區別。唯一的「亮點」是如果構造函數改變,我會得到異常。首先,我希望編寫最快的選項,創建新實例並設置單個屬性。但是,在這種方法中,如果引入新屬性,我不會得到異常。在不知道它的情況下基本製作無效副本。你的課程樣本涵蓋了這個問題。 –