如何在C#中克隆一个通用列表?

751

我在C#中有一个通用对象列表,并希望克隆该列表。列表中的项是可克隆的,但似乎没有list.Clone()选项。

这个问题有简单的解决方法吗?


56
你应该说明你需要深拷贝还是浅拷贝。 - orip
13
深复制和浅复制是什么? - Colonel Panic
6
@ColonelPanic http://en.wikipedia.org/wiki/Object_copy#Shallow_copy - Nathan Koop
17
浅拷贝会比指针拷贝多复制一层。例如,对列表进行浅拷贝将拷贝相同的元素,但生成一个不同的列表。 - orip
3
深拷贝会创建一个新的列表,其中包含全新的项目,但内容相同。 - user4843530
显示剩余2条评论
29个回答

689

如果您的元素是值类型,那么您可以直接进行如下操作:

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

但是,如果它们是引用类型并且您想要进行深拷贝(假设您的元素正确实现了ICloneable接口),您可以像这样操作:

List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);

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

显然,将上面的泛型和转换中的ICloneable替换为您的元素类型(实现了ICloneable)。

如果您的元素类型不支持ICloneable,但具有复制构造函数,则可以选择执行以下操作:

List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);

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

个人而言,我会避免使用ICloneable,因为需要保证所有成员的深度复制。相反,我建议使用拷贝构造函数或工厂方法,例如YourType.CopyFrom(YourType itemToCopy),该方法返回一个新的YourType实例。

这些选项中的任何一个都可以被封装在一个方法中(扩展或其他方式)。


2
@Dimitri:不,那不是真的。问题在于,当定义 ICloneable 时,定义从未说明克隆是深度还是浅层的,因此您无法确定对象实现它时将执行哪种类型的克隆操作。这意味着,如果您想要对 List<T> 进行深度克隆,则必须在没有 ICloneable 的情况下进行,以确保它是深度复制。 - Jeff Yates
7
为什么不使用AddRange方法?(newList.AddRange(oldList.Select(i => i.Clone())newList.AddRange(oldList.Select(i => new YourType(i)) - phoog
5
我认为扫描代码时它稍微难以阅读/理解,这就是全部。可读性对我来说很重要。 - Jeff Yates
2
@JeffYates:一个不够考虑周全的问题是,通常只有在存在某些执行路径会改变它们时才需要复制这些东西。很常见的情况是,不可变类型持有对可变类型实例的引用,但从不将该实例暴露给任何会改变它的东西。毫无必要地复制永远不会改变的东西有时可能会严重影响性能,使内存使用量成倍增加。 - supercat
1
我只是需要被提醒第一行 - 所以我更喜欢这个答案,谢谢@JeffYates! - Ian Grainger
显示剩余16条评论

458

您可以使用扩展方法。

static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}

77
我认为 List.ConvertAll 可能会更快地完成这个任务,因为它可以为列表预先分配整个数组,而不必一直调整大小。 - MichaelGG
2
@MichaelGG,如果您不想转换而只是克隆/复制列表中的项目,该怎么办?这样行吗?|| var clonedList = ListOfStrings.ConvertAll(p => p); - IbrarMumtaz
32
@IbrarMumtaz:这与var clonedList = new List<string>(ListOfStrings);相同。 - Brandon Arnold
4
不错的解决方案!顺便说一下,我更喜欢公共静态List<T> Clone<T>...这种方式。在像这样的情况下更加有用,因为不需要进行进一步的类型转换: List<MyType> cloned = listToClone.Clone(); - Plutoz
7
这只是答案的一半,因为它依赖于ICloneable接口的实现,而这是问题中最重要的部分。 - Everts
显示剩余5条评论

113

如果需要进行一次浅拷贝,您可以使用泛型List类的GetRange方法。

List<int> oldList = new List<int>( );
// Populate oldList...

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

摘自:泛型食谱


63
您也可以通过使用List<T>的构造函数来指定要从中复制的List<T>来实现此目的。例如,var shallowClonedList = new List<MyObject>(originalList); - Arkiliknam
13
我经常使用List<int> newList = oldList.ToList(),与此相同的效果。但是,在我看来,Arkiliknam的解决方案最易读懂。 - Dan Bechard
2
多年以后,但我更喜欢使用 ToList,因为它避免了所有的冗余 - 我想知道哪个实际上更高效...查了一下。看起来 ToList 调用了 new List<T>,最终会使用 Array.CopyTo,所以大致相同。 - NetMage

95
public static object DeepClone(object obj) 
{
    object objResult = null;

    using (var ms = new MemoryStream())
    {
        var bf = new BinaryFormatter();
        bf.Serialize(ms, obj);

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

     return objResult;
}

以下是使用C#和.NET 2.0的一种方法。您的对象需要[Serializable()]属性。目标是消除所有引用并构建新引用。


15
+1 - 我喜欢这个答案- 它快速、粗暴、丑陋但非常有效。我在 Silverlight 中使用它,并使用 DataContractSerializer 作为 BinarySerializer 不可用。何必写许多对象克隆代码,当你可以直接这样做呢? :) - slugster
3
我喜欢这个。虽然做事情“正确”很好,但是快速而粗略的方法通常也会派上用场。 - Odrade
3
快!但是:为什么脏? - raiserle
2
这个深度克隆非常快捷方便。但是要小心此页面上的其他建议。我尝试了几个,它们并没有进行深度克隆。 - RandallTo
2
唯一的负面方面,如果你愿意这样说的话,就是你的类必须被标记为可序列化才能使它起作用。 - Tuukka Haapaniemi
显示剩余2条评论

52

要克隆一个列表,只需调用 .ToList()。这将创建一个浅拷贝。

Microsoft (R) Roslyn C# Compiler version 2.3.2.62116
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> 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 }
> 

62
小心提醒,这是一份浅拷贝……这将创建两个列表对象,但其内部的对象将是相同的。也就是说,更改一个属性将会改变原始列表中同一对象/属性的值。 - Mark G
@MarkG 只要你使用值类型,这是安全的。 - Nour

24
稍作修改后,您也可以进行克隆操作:
public static T DeepClone<T>(T obj)
{
    T objResult;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, obj);
        ms.Position = 0;
        objResult = (T)bf.Deserialize(ms);
    }
    return objResult;
}

不要忘记 T 应该是可序列化的,否则会出现 System.Runtime.Serialization.SerializationException。 - Bence Végert
好的答案。一个提示:你可以把 if (!obj.GetType().IsSerializable) return default(T); 添加为第一条语句,这样就可以避免异常了。如果你把它改成扩展方法,甚至可以使用 Elvis 操作符,像这样 **var b = a?.DeepClone();**(例如给定 var a = new List<string>() { "a", "b" };)。 - Matt
BinaryFormatter在.NET 7中已被弃用。请参阅使用BinaryFormatter和相关类型的反序列化风险 - kkuilla

18

除非您需要每个对象的实际克隆,否则克隆列表的最佳方法是使用旧列表作为集合参数创建新列表。

List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);
< p >对myList进行的更改,例如插入或删除操作,不会影响cloneOfMyList,反之亦然。

但是,这两个列表包含的实际对象仍然是相同的。


1
我同意user49126的观点,这是一份浅拷贝,对一个列表所做的更改会反映在另一个列表中。 - Seidleroni
2
@Seidleroni,你错了。对列表项所做的更改会影响到另一个列表,而对列表本身的更改则不会。 - Wellington Zanelli
2
这是浅拷贝。 - Elliot Chen
这怎么是浅拷贝? - mko
3
刚确认,从myList中移除一个元素也会在cloneOfMyList中移除。 - Nick Gallimore
如果是真的,那么这将是List实现中一个令人惊讶的bug。新列表的定义是它在创建时包含相同的项,但具有自己的“状态”。每个项有两个对它的引用,但列表本身的任何操作都应该是独立的。 - ToolmakerSteve

17

使用 AutoMapper(或您喜欢的任何映射库)进行克隆操作非常简单且易于维护。

定义您的映射:

Mapper.CreateMap<YourType, YourType>();

施展魔法:

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);

16
如果您只关心值类型,并且您知道该类型:
List<int> newList = new List<int>(oldList);

如果您之前不知道类型,您需要使用帮助函数:

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

只需这样做:
List<string> myNewList = Clone(myOldList);

20
这并不会复制元素。 - Jeff Yates

12
如果您的项目中已经引用了Newtonsoft.Json并且您的对象可序列化,那么您可以始终使用:

如果你在你的项目中已经引用了Newtonsoft.Json,并且你的对象是可序列化的,你可以随时使用:

List<T> newList = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(listToCopy))

可能不是最有效的方法,但除非你要执行数以千计的操作,否则甚至可能注意不到速度差异。


6
重点不在于速度差异,而在于可读性。如果我看到这一行代码,我会拍拍自己的脑袋,想知道为什么要引入一个第三方库来序列化和反序列化一个我不知道为什么需要这样做的对象。此外,对于具有循环结构的模型列表中的对象,这种方法将无法工作。 - Jonathon Cwik
2
这段代码在深度克隆方面对我非常有效。该应用程序正在将文档样板从开发环境迁移到QA和生产环境。每个对象都是几个文档模板对象的数据包,而每个文档又由一系列段落对象组成。这段代码让我能够序列化.NET“源”对象,并立即将它们反序列化为新的“目标”对象,然后将其保存到不同环境下的SQL数据库中。 经过大量研究,我找到了很多东西,其中很多都太繁琐了,于是决定尝试这种方法。这种简短而灵活的方法正合适! - Developer63

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接