如何在C#中克隆泛型列表?

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

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

820163 次浏览

对于浅拷贝,您可以改为使用泛型List类的GetRange方法。

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


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

引用自:泛型配方

如果你的元素是值类型,那么你可以这样做:

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的新实例。

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

如果你只关心值类型…

你知道类型:

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

您可以使用扩展方法。

static class Extensions
{
public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
{
return listToClone.Select(item => (T)item.Clone()).ToList();
}
}
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()]。目标是丢失所有引用并构建新引用。

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

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

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

使用AutoMapper(或任何你喜欢的映射库)来克隆很简单,而且很容易维护。

定义你的映射:

Mapper.CreateMap<YourType, YourType>();

做魔术:

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

您可以使用扩展方法:

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

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

我为自己做了一些扩展,它转换了不实现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();
}
}

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

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

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

我使用自动复制器来复制一个对象。我只是设置了一个映射,将一个对象映射到它自己。您可以以任何您喜欢的方式包装此操作。

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

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

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

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

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

以下代码应以最小的更改传输到列表中。

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

// 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秒内克隆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;

结果:带有内部对象的个人实例-16.4,个人实例=null-5.6

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

我还没有测试序列化,但我怀疑一百万个类的改进。我会尝试一些快速的原型/牛顿。

附言:为了阅读简单起见,我在这里只使用了自动属性。我可以使用FieldInfo更新,或者您应该自己轻松实现它。

我最近使用开箱即用的DeepClone函数测试了协议缓冲区序列化器。它在一百万个简单对象上以4.2秒获胜,但当涉及内部对象时,它以7.4秒的结果获胜。

Serializer.DeepClone(personList);

摘要:如果你没有访问类的权限,那么这将有所帮助。否则它取决于对象的数量。我认为你可以使用反射多达10,000个对象(也许少一点),但对于超过这个数的协议缓冲区序列化器会表现得更好。

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

public static List<T> Clone<T>(this List<T> oldList)
{
var newList = new List<T>(oldList.Capacity);
newList.AddRange(oldList);
return newList;
}
    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;
}
}

没有必要将类标记为可序列化,在我们的测试中,使用Newtonsoft JsonSerializer甚至比使用BinaryFor的速度更快。使用可用于每个对象的扩展方法。

<强>关注:私有成员不被克隆

标准。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);
}

有一种简单的方法可以使用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;

要克隆列表,只需调用. 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 }
>
 //try this
List<string> ListCopy= new List<string>(OldList);
//or try
List<T> ListCopy=OldList.ToList();

如果有人读过这篇文章,我会很幸运……但是为了不在我的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列表(而IClonable接口希望我的方法返回一个对象列表):

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

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

在这种情况下,使用强制转换可能会对浅拷贝有所帮助:

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

对于深度拷贝,IClonable是正确的解决方案,但这里有一个类似的方法,使用构造函数而不是IClonable接口。

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使其简洁明了。同样,你可以按照其他答案的建议做并制作扩展方法等,但这些都不是必要的。

对于深度克隆,我使用反射如下:

public List<T> CloneList<T>(IEnumerable<T> listToClone) {
Type listType = listToClone.GetType();
Type elementType = listType.GetGenericArguments()[0];
List<T> listCopy = new List<T>();
foreach (T item in listToClone) {
object itemCopy = Activator.CreateInstance(elementType);
foreach (PropertyInfo property in elementType.GetProperties()) {
elementType.GetProperty(property.Name).SetValue(itemCopy, property.GetValue(item));
}
listCopy.Add((T)itemCopy);
}
return listCopy;
}

您可以互换地使用List或IENumable。

如果我需要深度复制的集合,我有喜欢的方法像这样:

public static IEnumerable<T> DeepCopy<T>(this IEnumerable<T> collectionToDeepCopy)
{
var serializedCollection = JsonConvert.SerializeObject(collectionToDeepCopy);
return JsonConvert.DeserializeObject<IEnumerable<T>>(serializedCollection);
}