如何在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个回答

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

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

这是一个非常基本的示例,在实际编程中没有用处。你必须克隆一个由具有其他对象列表子项的复杂对象列表组成的列表。 - Alexandru Dicu

1
你可以使用 List<T>.ConvertAll(Converter<T, T>) 方法创建一个新列表,其中包含原始列表的所有元素,并使用返回输入值的转换函数。
List<int> originalList = new List<int> { 1, 2, 3, 4, 5 };
List<int> clonedList = new List<int>(originalList.ConvertAll(x => x));

1
我使用Automapper复制一个对象。我只需设置一个将一个对象映射到自身的映射。您可以以任何您喜欢的方式包装此操作。

http://automapper.codeplex.com/


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。

P.S.:为了阅读简便起见,我只在此处使用了自动属性。我可以使用FieldInfo进行更新,或者您可以轻松地自己实现此功能。

最近我使用Protocol Buffers序列化器对DeepClone函数进行了测试。它在一百万个简单对象上获胜,用时4.2秒,但当涉及到内部对象时,它的结果为7.4秒。

Serializer.DeepClone(personList);

摘要:如果您无法访问这些类,则此方法将有所帮助。否则,它取决于对象的数量。我认为您可以使用反射来处理多达10,000个对象(可能略少一些),但对于更多的对象,Protocol Buffers序列化程序将表现更好。


0

我为自己制作了一些扩展,可以将未实现IClonable接口的项目集合进行转换。

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

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

这不起作用;克隆数组中的值更改仍会更改原始列表中的值。 - Bernoulli Lizard
你可以使用@IbrarMumtaz提供的代码var clonedList = ListOfStrings.ConvertAll(p => p);。它能够有效地工作。对一个列表所做的更改仅保留在该列表中,不会反映在另一个列表中。 - zainul

0

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

您可以创建一个扩展类:

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;

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

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

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或IEnumerable。


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