C# 不用 foreach 做 List 拷贝

How do I clone a generic list?

How do I copy items from list to list without foreach?

1. Shadow Copy

Shadow Copy call .ToList()

1
2
3
4
5
6
7
> var x = new List<int>() { 3, 4 };
> var y = x.ToList();
> x.Add(5)
> x
List<int>(3) { 3, 4, 5 }
> y
List<int>(2) { 3, 4 }

Shadow Copy Use New IEnumerable

1
2
List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);

If you don’t know the type before, you’ll need a helper function:

1
2
3
4
List<T> Clone<T>(IEnumerable<T> oldList)
{
return newList = new List<T>(oldList);
}

The just:

1
List<string> myNewList = Clone(myOldList);

Shadow Copy Use GetRange

1
2
3
4
List<int> oldList = new List<int>( );
// Populate oldList...

List<int> newList = oldList.GetRange(0, oldList.Count);

Shadow Copy Use AddRange

1
2
3
List<int> oldList = new List<int>( );
List<int> newList = new List<int>( );
newList.AddRange(oldList);

Shadow Copy Use Map

1
2
3
4
5
6
7
8
var List1= new List<Entities1>();
var List2= new List<Entities2>();
var List2 = List1.Select(p => new Entities2
{
EntityCode = p.EntityCode,
EntityId = p.EntityId,
EntityName = p.EntityName
}).ToList();

2. Deep Copy

DeepCopy with BinaryFormatter

Your object requires to be [Serializable()]. The goal is to lose all references and build new ones.

1
2
3
4
5
6
7
8
9
10
11
12
13
public static object DeepClone(object obj)
{
object objResult = null;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);

ms.Position = 0;
objResult = bf.Deserialize(ms);
}
return objResult;
}

DeepCopy implement ICloneable or use copy-constructor

this is a very good method!

If your elements are value types

1
List<YourType> newList = new List<YourType>(oldList);

However, if they are reference types and you want a deep copy (assuming your elements properly implement ICloneable), you could do something like this:

1
2
3
4
5
6
7
List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);

oldList.ForEach((item) =>
{
newList.Add((ICloneable)item.Clone());
});

If your element type doesn’t support ICloneable but does have a copy-constructor, you could do this instead:

1
2
3
4
5
6
7
List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);

oldList.ForEach((item)=>
{
newList.Add(new YourType(item));
});

Personally, I would avoid ICloneable because of the need to guarantee a deep copy of all members. Instead, I’d suggest the copy-constructor or a factory method like YourType.CopyFrom(YourType itemToCopy) that returns a new instance of YourType.

3. Some Demos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;

namespace DemoDotNet
{
public class CListToList
{
public CListToList()
{

}

public void Test()
{
List<Item> original = new List<Item>();
for (int i = 0; i < 2; ++i)
{
Item item = new Item();
original.Add(item);
}

Console.WriteLine("-------==START==-----------");
PrintList(original);

//使用Linq的ToList
UseLinqToList(original);

//使用AddRange
UseAddRange(original);

//深拷贝方式,需要当前类型可序列化
UseCopyList(original);

//使用重新生成的方式,没啥用
UseSelectNew(original);
}

private void UseLinqToList(List<Item> original)
{
List<Item> copy = original.ToList();
foreach (var v in copy)
{
v.a++;
v.b++;
}

Console.WriteLine("------->UseLinqToList--------");
PrintList(original);
Console.WriteLine(">>>");
PrintList(copy);
}

private void UseAddRange(List<Item> original)
{
List<Item> copy = new List<Item>();
copy.AddRange(original);

foreach (var v in copy)
{
v.a++;
v.b++;
}

Console.WriteLine("------->UseAddRange--------");
PrintList(original);
Console.WriteLine(">>>");
PrintList(copy);
}

private void UseCopyList(List<Item> original)
{
List<Item> copy = original.CopyList();

foreach (var v in copy)
{
v.a++;
v.b++;
}

Console.WriteLine("------->UseCopyList--------");
PrintList(original);
Console.WriteLine(">>>");
PrintList(copy);
}

private void UseSelectNew(List<Item> original)
{
List<Item> copy = original.Select(p => new Item()).ToList();

Console.WriteLine("------->UseSelectNew--------");
PrintList(original);
Console.WriteLine(">>>");
PrintList(copy);
}

private void PrintList(List<Item> list)
{

foreach (var v in list)
{
Console.WriteLine($"{v.a}, {v.b}");
}
}

[System.Serializable]
public class Item
{
public int a;
public int b;
}
}

public static class ListEx
{
//This method will create a copy of your list but your type should be serializable.
public static List<T> CopyList<T>(this List<T> lst)
{
List<T> lstCopy = new List<T>();
foreach (var item in lst)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, item);
stream.Position = 0;
lstCopy.Add((T)formatter.Deserialize(stream));
}
}
return lstCopy;
}
}
}