2010-11-19 137 views
14

我試圖做一個通用列表的深層副本,並想知道是否有任何其他方式,然後創建複製方法,並實際上每次複製一個成員。我有一個類,它看起來有點像這樣:列表的深層副本<T>

public class Data 
{    
    private string comment; 
    public string Comment 
    { 
     get { return comment; } 
     set { comment = value; } 
    } 

    private List<double> traceData; 
    public List<double> TraceData 
    { 
     get { return traceData; } 
     set { traceData = value; } 
    } 
} 

而且我有上述數據的列表,即List<Data>。我想要做的是將List的子集的跟蹤數據繪製到圖上,可能需要進行一些縮放或掃描數據。我顯然不需要在列表中繪製所有內容,因爲它們不適合屏幕。

我最初嘗試使用List.GetRange()方法獲取列表的子集,但似乎List<double>正在被淺拷貝而不是深拷貝。當我再次使用List.GetRange()獲取子集時,我得到先前修改過的數據,而不是其他地方檢索到的原始數據。

任何人都可以給我一個方向如何解決這個問題嗎?非常感謝。

+0

Skeet編輯但不知道答案? (8-O – Brad 2010-11-19 16:02:03

+1

也許我錯過了一些東西,但是什麼是「列表」的「深層複製」?它是一個數字列表,它不像Button類的列表或其他可能需要成員的列表被複制? – CodingGorilla 2010-11-19 16:04:40

+0

我認爲他意味着他想要對每個Data數據對象進行深層複製,這意味着他需要複製列表而不是複製引用。 – mquander 2010-11-19 16:08:07

回答

10

的慣用方式來處理這在C#是在你的Data上執行ICloneable,然後編寫一個Clone方法來完成深層複製(然後可能是Enumerable.CloneRange方法,它可以一次克隆部分列表)。不是任何內置的技巧或框架方法,使它更容易。

除非內存和性能是一個真正的問題,否則我建議您儘量重新設計,以便在不可變的Data對象上運行。它會更簡單。

+2

+1:不可變是真的唯一的方法:)如果用戶不需要對列表進行索引訪問,那麼一個簡單的不可改變的堆棧就足夠了。 – Juliet 2010-11-19 17:18:55

+2

[IClonable近來被認爲是一個糟糕的主意](http://stackoverflow.com/questions/536349/why-no-icloneablet) – Liam 2014-03-19 17:05:00

3

最簡單的(但髒)的方式,是實施類ICloneable並使用下一個擴展方法:

public static IEnumerable<T> Clone<T>(this IEnumerable<T> collection) where T : ICloneable 
{ 
    return collection.Select(item => (T)item.Clone()); 
} 

用法:

var list = new List<Data> { new Data { Comment = "comment", TraceData = new List { 1, 2, 3 } }; 
var newList = list.Clone(); 
+2

爲什麼這個「髒」? – Brad 2010-11-19 16:05:01

+4

ICloneable就像一個皮疹,開始抓撓,然後才知道它到處都是。 – jonnii 2010-11-19 16:06:37

+2

需要注意的是'item.Clone()'不能保證深度複製,'.MemberwiseClone()'方法創建一個內部成員的淺表副本。看msdn:http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx – Juliet 2010-11-19 16:08:29

0

如果你讓你的對象不可變的,你不必擔心身邊掠過它們的副本,那麼你可以這樣做:

var toPlot = list.Where(d => d.ShouldBePlotted()); 
1

你可以做的另一件事是你的類如serializable和使用二進制序列化。 這裏是一個工作示例

public class Program 
    { 
     [Serializable] 
     public class Test 
     { 
      public int Id { get; set; } 
      public Test() 
      { 

      } 
     } 

     public static void Main() 
     { 
      //create a list of 10 Test objects with Id's 0-10 
      List<Test> firstList = Enumerable.Range(0,10).Select(x => new Test { Id = x }).ToList(); 
      using (var stream = new System.IO.MemoryStream()) 

      { 
       var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); 
       binaryFormatter.Serialize(stream, firstList); //serialize to stream 
       stream.Position = 0; 
       //deserialize from stream. 
       List<Test> secondList = binaryFormatter.Deserialize(stream) as List<Test>; 
      } 


      Console.ReadKey(); 
     } 
    } 
+0

大多數時候,如果你願意,我建議你手動實施深層複製因爲序列化並不完全知道它的超快速度。但是,過去我實際上已經使用過這種風格的*巨大*可變數據樹,它在實際應用中工作得很好。 – Juliet 2010-11-20 17:25:04

+0

@Juliet,謝謝,我只是提供了一個替代解決方案的已發佈的。 – 2010-11-20 17:29:14

0

由於您的收藏是可變的,你需要以編程方式實現深拷貝:

public class Data 
{ 
    public string Comment { get; set; } 
    public List<double> TraceData { get; set; } 

    public Data DeepCopy() 
    { 
     return new Data 
     { 
      Comment = this.Comment, 
      TraceData = this.TraceData != null 
       ? new List<double>(this.TraceData) 
       : null; 
     } 
    } 
} 

Comment場可淺複製,因爲它已經是一個不可變類。您需要爲TraceData創建一個新列表,但這些元素本身是不可變的,不需要特殊處理來複制它們。

當我的子集中再次使用 List.GetRange(),我得到先前 修改的數據,無其它檢索到的原始數據 。

使用新DeepCopy方法,例如:

var pointsInRange = dataPoints 
    .Select(x => x.DeepCopy()) 
    .GetRange(start, length); 
6

您可以嘗試在代碼項目這個

public static object DeepCopy(object obj) 
    { 
     if (obj == null) 
      return null; 
     Type type = obj.GetType(); 

     if (type.IsValueType || type == typeof(string)) 
     { 
      return obj; 
     } 
     else if (type.IsArray) 
     { 
      Type elementType = Type.GetType(
       type.FullName.Replace("[]", string.Empty)); 
      var array = obj as Array; 
      Array copied = Array.CreateInstance(elementType, array.Length); 
      for (int i = 0; i < array.Length; i++) 
      { 
       copied.SetValue(DeepCopy(array.GetValue(i)), i); 
      } 
      return Convert.ChangeType(copied, obj.GetType()); 
     } 
     else if (type.IsClass) 
     { 

      object toret = Activator.CreateInstance(obj.GetType()); 
      FieldInfo[] fields = type.GetFields(BindingFlags.Public | 
         BindingFlags.NonPublic | BindingFlags.Instance); 
      foreach (FieldInfo field in fields) 
      { 
       object fieldValue = field.GetValue(obj); 
       if (fieldValue == null) 
        continue; 
       field.SetValue(toret, DeepCopy(fieldValue)); 
      } 
      return toret; 
     } 
     else 
      throw new ArgumentException("Unknown type"); 
    } 

感謝DetoX83 article

+2

幾乎完美無瑕。但是,正如我在這種情況下,你有mscorlib或當前程序集之外的對象數組,你將需要使用'elementType = Type.GetType(type.AssemblyQualifiedName.Replace(「[]」,string.Empty)); ' – makoshichi 2015-02-13 12:57:12

5

如果IClonable的方式對你來說太棘手。我建議轉換成某種東西然後回來。它可以使用BinaryFormatter或像Servicestack.Text這樣的Json轉換器完成,因爲它是.Net中速度最快的一個。

代碼應該是這樣的:

MyClass mc = new MyClass(); 
string json = mc.ToJson(); 
MyClass mcCloned = json.FromJson<MyClass>(); 

mcCloned不會引用MC。

+1

ServiceStack使用GPL。您可能需要使用Json.Net – aloisdg 2016-07-04 11:27:22

0
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace DeepListCopy_testingSome 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      List<int> list1 = new List<int>(); 
      List<int> list2 = new List<int>(); 

      //populate list1 
      for (int i = 0; i < 20; i++) 
      { 
       list1.Add(1); 
      } 

      /////// 
      Console.WriteLine("\n int in each list1 element is:\n"); 
      /////// 

      foreach (int i in list1) 
      { 
       Console.WriteLine(" list1 elements: {0}", i); 
       list2.Add(1); 
      } 

      /////// 
      Console.WriteLine("\n int in each list2 element is:\n"); 
      /////// 

      foreach (int i in list2) 
      { 
       Console.WriteLine(" list2 elements: {0}", i); 
      } 

      ///////enter code here 

      for (int i = 0; i < list2.Count; i++) 
      { 
       list2[i] = 2; 
      } 



      /////// 
      Console.WriteLine("\n Printing list1 and list2 respectively to show\n" 
          + " there is two independent lists,i e, two differens" 
          + "\n memory locations after modifying list2\n\n"); 
      foreach (int i in list1) 
      { 
       Console.WriteLine(" Printing list1 elements: {0}", i); 
      } 

      /////// 
      Console.WriteLine("\n\n"); 
      /////// 

      foreach (int i in list2) 
      { 
       Console.WriteLine(" Printing list2 elements: {0}", i); 
      } 

      Console.ReadKey(); 
     }//end of Static void Main 
    }//end of class 
} 
+0

嘗試使用不是基元的對象集合。那麼你的例子將無法工作。編輯:那麼現在我看到你甚至沒有列表1填充列表2!這與問題無關。 – 2017-02-27 15:58:56

0

一個快速通用方式深深序列化一個對象是使用JSON.net。以下擴展方法允許序列化任意任意對象的列表,但能夠跳過實體框架導航屬性,因爲這些可能導致循環依賴和不需要的數據提取。

方法

public static List<T> DeepClone<T>(this IList<T> list, bool ignoreVirtualProps = false) 
{ 
    JsonSerializerSettings settings = new JsonSerializerSettings(); 
    if (ignoreVirtualProps) 
    { 
     settings.ContractResolver = new IgnoreNavigationPropsResolver(); 
     settings.PreserveReferencesHandling = PreserveReferencesHandling.None; 
     settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; 
     settings.Formatting = Formatting.Indented; 
    } 

    var serialized = JsonConvert.SerializeObject(list, settings); 
    return JsonConvert.DeserializeObject<List<T>>(serialized); 
} 

使用

var clonedList = list.DeepClone(); 

默認情況下,JSON.NET只序列化公共屬性。如果私人財產也必須克隆,可以使用this solution

該方法允許quick (de)serializationcomplex hierarchies of objects