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


592

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

有没有解决此问题的简单方法?


44
您应该说,如果您要寻找深层副本或浅层副本
Orip

10
什么是深浅副本?
Panic Panic 2012年


3
@orip clone()根据定义不是深拷贝吗?我认为在C#中,您可以轻松地使用=传递指针。
克里斯(Chris

13
@Chris浅表副本比指针副本深一层。例如,列表的浅表副本将具有相同的元素,但将是不同的列表。
orip

Answers:


385

您可以使用扩展方法。

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

71
我认为List.ConvertAll可能会在更快的时间内完成此操作,因为它可以为列表预分配整个数组,而不必一直调整大小。
MichaelGG

2
@MichaelGG,如果您不想转换而只是克隆/复制列表中的项目怎么办?这行得通吗?|| var clonedList = ListOfStrings.ConvertAll(p => p);
IbrarMumtaz 2014年

29
@IbrarMumtaz:与var clonedList = new List <string>(ListOfStrings)相同;
Brandon Arnold 2014年

4
不错的解决方案!顺便说一句,我更喜欢公共静态List <T> CLone <T>...。在这种情况下它更加有用,因为不需要进一步的强制转换:List <MyType> cloned = listToClone.Clone();。
Plutoz

2
这是深度克隆
George Birbilis15年

511

如果您的元素是值类型,则可以执行以下操作:

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

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


1
我认为List <T> .ConvertAll可能比创建新列表并执行foreach + add更好。
MichaelGG

2
@Dimitri:不,那不是真的。问题是,ICloneable定义后,定义永远不会说明克隆是深克隆还是浅克隆,因此您无法确定在对象实现克隆操作时将执行哪种类型的克隆操作。这意味着,如果您想对进行深层克隆List<T>,则必须执行此操作而不必ICloneable确保它是深层副本。
杰夫·耶茨

5
为什么不使用AddRange方法?(newList.AddRange(oldList.Select(i => i.Clone())newList.AddRange(oldList.Select(i => new YourType(i)
phoog 2010年

5
@phoog:我认为在扫描代码时,它的可读性/可理解性稍差一些。可读性对我来说很重要。
杰夫·耶茨

1
@JeffYates:一个没有充分考虑到的皱纹是,通常只有在存在某些会使它们变异的执行路径时才需要复制它们。使不可变类型持有对可变类型实例的引用是常见的,但永远不要将该实例暴露给任何会对其进行突变的对象。不必要地复制永远不会改变的内容有时会成为主要的性能消耗,使内存使用量增加几个数量级。
超级猫

84

对于浅表副本,可以改用通用List类的GetRange方法。

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

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

引用自:泛型食谱


43
您还可以通过使用List <T>的构造方法指定要从中复制的List <T>来实现。例如varshallowClonedList = new List <MyObject>(originalList);
Arkiliknam'2

9
我经常用List<int> newList = oldList.ToList()。效果一样。但是,我认为Arkiliknam的解决方案最易于阅读。
丹·贝查德

82
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;
}

这是使用C#和.NET 2.0的一种方法。您的对象必须为[Serializable()]。目标是丢失所有参考并建立新的参考。


11
+1-我喜欢这个答案-快速,肮脏,令人讨厌且非常有效。我在Silverlight中使用过,并使用了DataContractSerializer,因为BinarySerializer不可用。当您可以执行此操作时,谁需要编写对象克隆代码页?:)
slugster 2010年

3
我喜欢这个。虽然“做对”事情很好,但经常又麻烦又麻烦。
奥德拉德(Odrade)2010年

3
快!但是:为什么脏?
抬头

2
这种深深的克隆既快速又容易。请仔细阅读本页上的其他建议。我尝试了几次,但它们并未深入克隆。
兰德尔

2
如果可以这样称呼,那么唯一的消极方面是您的类必须标记为Serializable才能起作用。
Tuukka Haapaniemi

29

要克隆列表,只需调用.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 }
> 

3
迄今为止最简单的解决方案
Curveorzos

28
有点警告,这是一个浅表副本。这将创建两个列表对象,但内部的对象将相同。即,更改一个属性将更改原始列表中的相同对象/属性。
Mark G

22

稍作修改后,您还可以克隆:

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。
BenceVégert'17

好答案。一个提示:您可以添加if (!obj.GetType().IsSerializable) return default(T);第一条防止异常的语句。而且,如果将其更改为扩展方法,甚至可以使用Elvis运算符,例如var b = a?.DeepClone();var a = new List<string>() { "a", "b" }; 举个例子)。
马特

15

除非您需要对内部的每个对象进行实际克隆,否则List<T>克隆列表的最佳方法是使用旧列​​表作为收集参数来创建新列表。

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

myList诸如插入或删除之类的更改不会影响cloneOfMyList,反之亦然。

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


我同意user49126,我看到它是一个浅表副本,对一个列表所做的更改反映在另一个列表中。
Seidleroni

1
@Seidleroni,你错了。对列表itens所做的更改会影响其他列表,而列表本身中的更改则不会。
惠灵顿扎纳利

这是浅表副本。
Elliot Chen

这是浅表吗?
mko

2
@WellingtonZanelli刚刚确认从myList中删除元素也会将其从cloneOfMyList中删除。
尼克·加里莫尔

13

使用AutoMapper(或您喜欢的任何映射库)进行克隆很简单,并且可以维护。

定义映射:

Mapper.CreateMap<YourType, YourType>();

做魔术:

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

13

如果您只关心值类型...

而且您知道类型:

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);

15
这不会克隆元素。
杰夫·耶茨

10

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

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

可能不是最有效的方法,但是除非您重复执行1000到100次,否则您甚至可能不会注意到速度差异。


4
这与速度差异无关,而与可读性有关。如果我来到这行代码,我会would之以鼻,想知道为什么他们引入了第三方库来序列化然后反序列化一个对象,而我不知道它为什么会发生。同样,这对于具有圆形结构的对象的模型列表也不起作用。
Jonathon Cwik

1
这段代码对我来说非常适合深度克隆。该应用程序正在将文档样板从Dev迁移到QA到Prod。每个对象都是几个文档模板对象的数据包,每个文档又由一个段落对象列表组成。该代码使我可以序列化.NET“源”对象,并立即将其反序列化为新的“目标”对象,然后将其保存到其他环境中的SQL数据库中。经过大量研究,我发现了很多东西,其中很多东西太麻烦了,因此决定尝试一下。这种简短而灵活的方法是“正确的”!
Developer63

3
public static Object CloneType(Object objtype)
{
    Object lstfinal = new Object();

    using (MemoryStream memStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
        binaryFormatter.Serialize(memStream, objtype); memStream.Seek(0, SeekOrigin.Begin);
        lstfinal = binaryFormatter.Deserialize(memStream);
    }

    return lstfinal;
}

3
public class CloneableList<T> : List<T>, ICloneable where T : ICloneable
{
  public object Clone()
  {
    var clone = new List<T>();
    ForEach(item => clone.Add((T)item.Clone()));
    return clone;
  }
}

3
    public List<TEntity> Clone<TEntity>(List<TEntity> o1List) where TEntity : class , new()
    {
        List<TEntity> retList = new List<TEntity>();
        try
        {
            Type sourceType = typeof(TEntity);
            foreach(var o1 in o1List)
            {
                TEntity o2 = new TEntity();
                foreach (PropertyInfo propInfo in (sourceType.GetProperties()))
                {
                    var val = propInfo.GetValue(o1, null);
                    propInfo.SetValue(o2, val);
                }
                retList.Add(o2);
            }
            return retList;
        }
        catch
        {
            return retList;
        }
    }

3

我的朋友Gregor Martinovic和我想出了一个使用JavaScript序列化器的简单解决方案。不需要将类标记为Serializable,在我们的测试中使用Newtonsoft JsonSerializer比使用BinaryFormatter更快。使用可在每个对象上使用的扩展方法。

标准.NET JavascriptSerializer选项:

public static T DeepCopy<T>(this T value)
{
    JavaScriptSerializer js = new JavaScriptSerializer();

    string json = js.Serialize(value);

    return js.Deserialize<T>(json);
}

使用Newtonsoft JSON的更快选项:

public static T DeepCopy<T>(this T value)
{
    string json = JsonConvert.SerializeObject(value);

    return JsonConvert.DeserializeObject<T>(json);
}

2
私有成员不会使用JSON方法克隆。stackoverflow.com/a/78612/885627
himanshupareek66

3
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();

3

如果有人读过我会很幸运...但是为了不在我的Clone方法中返回类型对象的列表,我创建了一个接口:

public interface IMyCloneable<T>
{
    T Clone();
}

然后我指定了扩展名:

public static List<T> Clone<T>(this List<T> listToClone) where T : IMyCloneable<T>
{
    return listToClone.Select(item => (T)item.Clone()).ToList();
}

这是我的A / V标记软件中接口的实现。我想让我的Clone()方法返回一个VidMark列表(而ICloneable接口希望我的方法返回一个对象列表):

public class VidMark : IMyCloneable<VidMark>
{
    public long Beg { get; set; }
    public long End { get; set; }
    public string Desc { get; set; }
    public int Rank { get; set; } = 0;

    public VidMark Clone()
    {
        return (VidMark)this.MemberwiseClone();
    }
}

最后,扩展在类中的用法:

private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;

//Other methods instantiate and fill the lists

private void SetUndoVidMarks()
{
    _UndoVidMarks = _VidMarks.Clone();
}

有人喜欢吗?有什么改善吗?


2

您还可以使用将该列表简单地转换为数组ToArray,然后使用克隆该数组Array.Clone(...)。根据您的需求,Array类中包含的方法可以满足您的需求。


这行不通;更改克隆数组中的值仍会更改原始列表中的值。
伯努利·利扎德

您可以使用var clonedList = ListOfStrings.ConvertAll(p => p); 正如@IbrarMumtaz给出的....有效地工作...对一个列表的更改将保留在自身之中,而不会反映在另一个列表中
zainul

2

您可以使用扩展方法:

namespace extension
{
    public class ext
    {
        public static List<double> clone(this List<double> t)
        {
            List<double> kop = new List<double>();
            int x;
            for (x = 0; x < t.Count; x++)
            {
                kop.Add(t[x]);
            }
            return kop;
        }
   };

}

您可以使用其值类型成员来克隆所有对象,例如,考虑此类:

public class matrix
{
    public List<List<double>> mat;
    public int rows,cols;
    public matrix clone()
    { 
        // create new object
        matrix copy = new matrix();
        // firstly I can directly copy rows and cols because they are value types
        copy.rows = this.rows;  
        copy.cols = this.cols;
        // but now I can no t directly copy mat because it is not value type so
        int x;
        // I assume I have clone method for List<double>
        for(x=0;x<this.mat.count;x++)
        {
            copy.mat.Add(this.mat[x].clone());
        }
        // then mat is cloned
        return copy; // and copy of original is returned 
    }
};

注意:如果您对副本(或克隆)进行任何更改,则不会影响原始对象。


2

如果您需要具有相同容量的克隆列表,则可以尝试以下操作:

public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}

1

我为自己做了一些扩展,可以转换未实现IClonable的项目的ICollection

static class CollectionExtensions
{
    public static ICollection<T> Clone<T>(this ICollection<T> listToClone)
    {
        var array = new T[listToClone.Count];
        listToClone.CopyTo(array,0);
        return array.ToList();
    }
}

似乎有些集合(例如,Silverlight的DataGrid的SelectedItems)跳过了CopyTo的实现,这种方法存在问题
George Birbilis


1

在这种情况下,使用强制转换对于浅表副本可能会有所帮助:

IList CloneList(IList list)
{
    IList result;
    result = (IList)Activator.CreateInstance(list.GetType());
    foreach (object item in list) result.Add(item);
    return result;
}

应用于通用列表:

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);

1

对于深层复制,ICloneable是正确的解决方案,但是这是使用构造函数而不是ICloneable接口的类似于ICloneable的方法。

public class Student
{
  public Student(Student student)
  {
    FirstName = student.FirstName;
    LastName = student.LastName;
  }

  public string FirstName { get; set; }
  public string LastName { get; set; }
}

// wherever you have the list
List<Student> students;

// and then where you want to make a copy
List<Student> copy = students.Select(s => new Student(s)).ToList();

您将需要以下库来进行复制

using System.Linq

您也可以使用for循环代替System.Linq,但是Linq使其简洁明了。同样,您可以按照其他答案的建议进行操作,并制作扩展方法等,但是这些都不是必需的。


这就是所谓的“复制构造函数”。这是一种容易出错的方法,每当您向Student中添加新字段时,都必须记住将其添加到副本构造函数中。“克隆”背后的主要思想是避免该问题。
kenno

2
即使使用ICloneable,您也必须在类上具有“克隆”方法。除非您使用反射(您也可以在上述方法中使用反射),否则Clone方法将看起来与上面的复制构造方法类似,并且会遇到必须为新的/更改的字段进行更新的问题。但这就是说“当类的字段更改时,必须更新该类”。当然可以;)
ztorstri

0

以下代码应以最小的更改转移到列表上。

基本上,它是通过在每个连续循环中从更大范围插入一个新的随机数来工作的。如果已经存在相同或更高的数字,则将这些随机数上移一个,以使它们转移到新的更大范围的随机索引中。

// Example Usage
int[] indexes = getRandomUniqueIndexArray(selectFrom.Length, toSet.Length);

for(int i = 0; i < toSet.Length; i++)
    toSet[i] = selectFrom[indexes[i]];


private int[] getRandomUniqueIndexArray(int length, int count)
{
    if(count > length || count < 1 || length < 1)
        return new int[0];

    int[] toReturn = new int[count];
    if(count == length)
    {
        for(int i = 0; i < toReturn.Length; i++) toReturn[i] = i;
        return toReturn;
    }

    Random r = new Random();
    int startPos = count - 1;
    for(int i = startPos; i >= 0; i--)
    {
        int index = r.Next(length - i);
        for(int j = startPos; j > i; j--)
            if(toReturn[j] >= index)
                toReturn[j]++;
        toReturn[i] = index;
    }

    return toReturn;
}

0

另一件事:您可以使用反射。如果您可以正确地缓存它,那么它将在5.6秒内克隆1,000,000个对象(使用内部对象则为16.4秒)。

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Person
{
       ...
      Job JobDescription
       ...
}

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Job
{...
}

private static readonly Type stringType = typeof (string);

public static class CopyFactory
{
    static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

    private static readonly MethodInfo CreateCopyReflectionMethod;

    static CopyFactory()
    {
        CreateCopyReflectionMethod = typeof(CopyFactory).GetMethod("CreateCopyReflection", BindingFlags.Static | BindingFlags.Public);
    }

    public static T CreateCopyReflection<T>(T source) where T : new()
    {
        var copyInstance = new T();
        var sourceType = typeof(T);

        PropertyInfo[] propList;
        if (ProperyList.ContainsKey(sourceType))
            propList = ProperyList[sourceType];
        else
        {
            propList = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            ProperyList.Add(sourceType, propList);
        }

        foreach (var prop in propList)
        {
            var value = prop.GetValue(source, null);
            prop.SetValue(copyInstance,
                value != null && prop.PropertyType.IsClass && prop.PropertyType != stringType ? CreateCopyReflectionMethod.MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { value }) : value, null);
        }

        return copyInstance;
    }

我通过使用Watcher类以一种简单的方式对其进行了测量。

 var person = new Person
 {
     ...
 };

 for (var i = 0; i < 1000000; i++)
 {
    personList.Add(person);
 }
 var watcher = new Stopwatch();
 watcher.Start();
 var copylist = personList.Select(CopyFactory.CreateCopyReflection).ToList();
 watcher.Stop();
 var elapsed = watcher.Elapsed;

结果:使用内部对象PersonInstance-16.4,PersonInstance = null-5.6

CopyFactory只是我的测试类,在这里我有十几个测试,包括表达式的用法。您可以在扩展程序中以其他形式实现此功能。不要忘记缓存。

我还没有测试序列化,但是我怀疑有百万级的改进。我将尝试快速的protobuf / newton。

PS:为了简化阅读,我在这里只使用自动属性。我可以使用FieldInfo进行更新,或者您应该轻松地自行实现。

我最近使用开箱即用的DeepClone功能测试了协议缓冲区序列化程序。在百万个简单对象上,它以4.2秒获胜,但是在内部对象上,它以7.4秒获胜。

Serializer.DeepClone(personList);

简介:如果您无权访问这些类,则将有所帮助。否则,它取决于对象的数量。我认为您最多可以使用10,000个对象的反射(可能会少一些),但除此之外,Protocol Buffers序列化程序的性能会更好。


0

有一种使用JSON序列化器和反序列化器在C#中克隆对象的简单方法。

您可以创建一个扩展类:

using Newtonsoft.Json;

static class typeExtensions
{
    [Extension()]
    public static T jsonCloneObject<T>(T source)
    {
    string json = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(json);
    }
}

要克隆并反对:

obj clonedObj = originalObj.jsonCloneObject;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.