Как клонировать общий список в C#?

У меня есть общий список объектов в C# и я хочу клонировать список. Элементы в списке клонируются, но, похоже, нет возможности сделать list.Clone().

есть простой способ обойти это?

23 ответов


вы можете использовать метод расширения.

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

если ваши элементы являются типами значений, то вы можете просто сделать:

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 В приведенных выше дженериках и cast с любым типом элемента, который реализует 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.

любой из этих параметров может быть обернут методом (расширением или иным способом).


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 универсального класса List.

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

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

ЦИТ. по: Универсальные Рецепты


после небольшой модификации вы также можете клонировать:

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

Если вам не нужен фактический клон каждого отдельного объекта внутри вашего List<T>, лучший способ клонировать список-создать новый список со старым списком в качестве параметра коллекции.

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

изменения myList такие как insert или remove не повлияют на cloneOfMyList и наоборот.

фактические объекты, которые содержат два списка, по-прежнему одинаковы.


используйте AutoMapper (или любое отображение lib, которое вы предпочитаете) для клонирования просто и много ремонтопригодно.

определите свое отображение:

Mapper.CreateMap<YourType, YourType>();

сделать волшебство:

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

клонировать список просто позвоните .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 }
> 

Если вы заботитесь только о типах значений...

и вы знаете, типа:

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

Если вы уже ссылались на Newtonsoft.Json в вашем проекте и ваши объекты сериализуемы, вы всегда можете использовать:

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

возможно, не самый эффективный способ сделать это, но если вы не делаете это 100 раз из 1000 раз, вы можете даже не заметить разницу в скорости.


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

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

вы также можете просто преобразовать список в массив с помощью ToArray, а затем клонировать массив, используя Array.Clone(...). В зависимости от ваших потребностей методы, включенные в класс Array, могут удовлетворить ваши потребности.


Вы можете использовать метод расширения:

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

Примечание: Если вы сделаете какие-либо изменения в копии (или клонировании), это не повлияет на исходный объект.


Если вам нужен клонированный список с той же емкостью, вы можете попробовать следующее:

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

мой друг Грегор Мартинович и я придумали это простое решение, используя сериализатор JavaScript. Нет необходимости помечать классы как сериализуемые и в наших тестах с использованием 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);
}

Я сделал для своего собственного расширения, которое преобразует ICollection элементов, которые не реализуют 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();
    }
}

Я использую automapper для копирования объекта. Я просто настраиваю отображение, которое сопоставляет один объект с самим собой. Вы можете завершить эту операцию любым способом.

http://automapper.codeplex.com/


следующий код следует перенести в список с минимальными изменениями.

в основном он работает, вставляя новое случайное число из большего диапазона с каждым последовательным циклом. Если уже существуют числа, которые совпадают или превышают его, сдвиньте эти случайные числа на единицу, чтобы они перешли в новый больший диапазон случайных индексов.

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

еще одна вещь: вы можете использовать отражение. Если вы правильно кэшируете это, то он клонирует 1 000 000 объектов за 5,6 секунды (к сожалению, 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.: Для простоты чтения я использовал здесь только auto-property. Я мог бы обновить с помощью FieldInfo, или вы должны легко реализовать это самостоятельно.

Я недавно протестировала Протокол Буферы сериализатор с функция DeepClone из коробки. Он выигрывает 4,2 секунды на миллионе простых объектов, но когда дело доходит до внутренних объектов, он выигрывает с результатом 7,4 секунды.

Serializer.DeepClone(personList);

резюме: если у вас нет доступа к классам, то это поможет. В противном случае это зависит от количества объектов. Я думаю, вы можете использовать отражение до 10 000 объектов (может быть, немного меньше), но для большего, чем это, сериализатор буферов протокола будет работать лучше.


существует простой способ клонирования объектов в 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;

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