How do I clone a generic list in C#?
我在C中有一个对象的通用列表,并希望克隆该列表。列表中的项目是可克隆的,但似乎没有选择执行
有什么简单的方法可以解决这个问题吗?
如果您的元素是值类型,那么您只需执行以下操作:
1 |
但是,如果它们是引用类型,并且您想要一个深度拷贝(假设您的元素正确地实现了
1 2 3 4 5 6 7 |
显然,在上述泛型中替换
如果您的元素类型不支持
1 2 3 4 5 6 7 |
就我个人而言,我会避免使用
这些选项中的任何一个都可以用方法(扩展或其他方法)包装。
您可以使用扩展方法。
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 |
这是一种使用C和.NET 2.0实现此功能的方法。你的目标必须是
对于一个浅拷贝,您可以使用泛型列表类的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 |
要克隆列表,只需调用.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 } > |
除非需要实际克隆
1 2 |
对
但是,两个列表包含的实际对象仍然相同。
如果您只关心值类型…
你知道类型:
1 |
如果您以前不知道类型,则需要一个助手函数:
1 2 3 4 |
公正:
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 } }; |
注意:如果对复制(或克隆)进行任何更改,则不会影响原始对象。
您还可以使用
如果需要具有相同容量的克隆列表,可以尝试以下操作:
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 |
结果:内部对象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(); } |
有人喜欢吗?有什么改进吗?