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

How do I clone a generic list in C#?

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

有什么简单的方法可以解决这个问题吗?


如果您的元素是值类型,那么您只需执行以下操作:

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

但是,如果它们是引用类型,并且您想要一个深度拷贝(假设您的元素正确地实现了ICloneable),那么您可以这样做:

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

显然,在上述泛型中替换ICloneable,并使用实现ICloneable的元素类型来强制转换。

如果您的元素类型不支持ICloneable,但有一个复制构造函数,那么您可以这样做:

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

就我个人而言,我会避免使用ICloneable,因为我需要保证所有成员的完整副本。相反,我建议使用复制构造函数或像YourType.CopyFrom(YourType itemToCopy)这样的工厂方法,它返回YourType的新实例。

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


您可以使用扩展方法。

1
2
3
4
5
6
7
static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}


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

这是一种使用C和.NET 2.0实现此功能的方法。你的目标必须是[Serializable()]。目标是丢失所有引用并创建新的引用。


对于一个浅拷贝,您可以使用泛型列表类的getrange方法。

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

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

引自:仿制药配方


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

1
2
3
4
5
6
7
8
9
10
11
12
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;
}


要克隆列表,只需调用.to list()。

1
2
3
4
5
6
7
8
9
10
11
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 }
>


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

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

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

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


如果您只关心值类型…

你知道类型:

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

如果您以前不知道类型,则需要一个助手函数:

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

公正:

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


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

定义映射:

1
Mapper.CreateMap<YourType, YourType>();

做魔术:

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


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

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

这可能不是最有效的方法,但是除非你做了100次1000次,否则你甚至可能没有注意到速度差。


1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    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;
        }
    }

1
2
3
4
5
6
7
8
9
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;
  }
}

您可以使用扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
        }
   };

}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
    }
};

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


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


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

1
2
3
4
5
6
public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}

我的朋友GregorMartinovic和我使用一个JavaScript序列化程序提出了这个简单的解决方案。没有必要将类标记为可序列化的,在我们的测试中,使用NewtonSoft JSonserializer比使用BinaryFormatter更快。在每个对象上都可以使用扩展方法。

标准.NET JavaScriptSerializer选项:

1
2
3
4
5
6
7
8
public static T DeepCopy<T>(this T value)
{
    JavaScriptSerializer js = new JavaScriptSerializer();

    string json = js.Serialize(value);

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

使用NewtonSoft JSON更快的选择:

1
2
3
4
5
6
public static T DeepCopy<T>(this T value)
{
    string json = JsonConvert.SerializeObject(value);

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


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

1
2
3
4
5
6
7
8
9
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();
    }
}


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

1
2
3
4
5
6
7
IList CloneList(IList list)
{
    IList result;
    result = (IList)Activator.CreateInstance(list.GetType());
    foreach (object item in list) result.Add(item);
    return result;
}

应用于通用列表:

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


我使用automapper复制一个对象。我只是设置了一个映射,将一个对象映射到它自己。您可以任意包装此操作。

网址:http://automapper.codeplex.com/


对于深度复制,ICloneable是正确的解决方案,但这里有一个类似的方法,即使用构造函数而不是ICloneable接口来实现ICloneable。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();

您将需要在下面的库中进行复制

1
using System.Linq

您也可以使用for循环而不是system.linq,但是linq使它简洁明了。同样,你也可以按照其他答案的建议来做,并制定扩展方法等,但这些都不是必需的。


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

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

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

另一件事:你可以使用反射。如果您能正确地缓存它,那么它将在5.6秒内克隆1000000个对象(遗憾的是,内部对象的克隆时间为16.4秒)。

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
[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类以一种简单的方式测量它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 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为空-5.6

CopyFactory只是我的测试类,我有十几个测试,包括表达式的使用。您可以在扩展或其他任何形式中以另一种形式实现这一点。不要忘记缓存。

我还没有测试序列化,但我怀疑一百万个类的改进。我要试试快一点的Protobuf/Newton。

另外:为了简单阅读,我只使用了auto属性。我可以用fieldinfo更新,或者您应该很容易地自己实现这个。

我最近用开箱即用的deepclone函数测试了协议缓冲区序列化程序。它在一百万个简单物体上以4.2秒获胜,但当涉及到内部物体时,它以7.4秒获胜。

1
Serializer.DeepClone(personList);

总结:如果您没有访问这些类的权限,那么这将有所帮助。否则,它取决于对象的计数。我认为您可以使用多达10000个对象的反射(可能稍微少一点),但是对于更多的对象,协议缓冲区序列化程序将执行得更好。


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

可以创建扩展类:

1
2
3
4
5
6
7
8
9
10
11
using Newtonsoft.Json;

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

要克隆和对象:

1
obj clonedObj = originalObj.jsonCloneObject;


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

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

1
2
3
4
public interface IMyCloneable<T>
{
    T Clone();
}

然后我指定了扩展名:

1
2
3
4
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接口希望我的方法返回一个对象列表):

1
2
3
4
5
6
7
8
9
10
11
12
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();
    }
}

最后,在类中使用扩展:

1
2
3
4
5
6
7
8
9
private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;

//Other methods instantiate and fill the lists

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

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