Як клонувати загальний список у C #?


592

У мене є загальний список об'єктів у C # і я хочу його клонувати. Елементи зі списку можуть бути клонованими, але, здається, немає можливості зробити це list.Clone().

Чи існує простий спосіб цього?


44
Вам слід сказати, якщо ви шукаєте глибоку копію або дрібну копію
відвідайте

10
Що таке глибокі та неглибокі копії?
Полковник Паніка


3
@orip Чи не clone()за визначенням це копія? У C # ви можете легко пропустити покажчики навколо, = подумав я.
Кріс

13
@Chris дрібну копію копіює на один рівень глибше, ніж копію вказівника. Наприклад, неглибока копія списку матиме однакові елементи, але буде іншим.
оріп

Відповіді:


385

Можна використовувати метод розширення.

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

71
Я думаю, що List.ConvertAll може це зробити за швидший час, оскільки він може попередньо виділити весь масив для списку, порівняно з необхідністю змінити розмір весь час.
MichaelGG

2
@MichaelGG, що робити, якщо ви не хочете конвертувати, а просто клонувати / дублювати елементи зі списку? Це працювало б? || var clonedList = ListOfStrings.ConvertAll (p => p);
IbrarMumtaz

29
@IbrarMumtaz: Це те саме, що і var clonedList = новий Список <string> (ListOfStrings);
Брендон Арнольд

4
Приємне рішення! До речі, я віддаю перевагу загальнодоступному статичному списку <T> CLone <T> ... Він корисніший у таких випадках, тому що подальший викид не потрібен: Список <MyType> cloned = listToClone.Clone ();
Плутоз

2
це глибоке клонування
Джордж Бірбіліс

511

Якщо ваші елементи - це типові значення, то ви можете просто зробити:

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.

Будь-який із цих варіантів може бути перетворений методом (розширенням чи іншим способом).


1
Я думаю, що список <T> .ConvertAll може виглядати приємніше, ніж створювати новий список і робити foreach + add.
MichaelGG

2
@Dimitri: Ні, це неправда. Проблема полягає в тому, що коли ICloneableбуло визначено, дефініція ніколи не вказувала, чи був клон глибоким чи неглибоким, тому ви не можете визначити, який тип операції Clone буде виконаний, коли об'єкт реалізує його. Це означає, що якщо ви хочете зробити глибокий клон List<T>, вам доведеться це зробити, не ICloneableбудучи впевненим, що це глибока копія.
Джефф Йейтс

5
Чому б не застосувати метод AddRange? ( newList.AddRange(oldList.Select(i => i.Clone())або newList.AddRange(oldList.Select(i => new YourType(i))
фог

5
@phoog: Я думаю, що при скануванні коду це трохи менш читабельно / зрозуміло, це все. Читання перемагає для мене.
Джефф Йейтс

1
@JeffYates: Одна недостатньо продумана зморшка полягає в тому, що речі, як правило, потрібно скопіювати лише у тому випадку, якщо існує якийсь шлях виконання, який би їх мутував. Це дуже поширене мати незмінні типи містять посилання на екземпляр змінюваного типу, але ніколи не піддавати цей екземпляр до чого - або , що буде мутувати його. Зайве копіювання речей, які ніколи не зміняться, іноді може стати головним витратою продуктивності, збільшуючи використання пам’яті на порядок.
supercat

84

Для дрібної копії можна замість цього використовувати метод GetRange загального класу List.

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

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

Цитується з: Рецепти генерики


43
Ви також можете досягти цього, скориставшись переліком списку <T>, щоб вказати Список <T>, з якого потрібно копіювати. наприклад, var shallowClonedList = новий список <MyObject> (originalList);
Arkiliknam

9
Я часто використовую List<int> newList = oldList.ToList(). Такий же ефект. Однак рішення Arkiliknam найкраще для читабельності на мій погляд.
Ден Бешард

82
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()]. Мета - втратити всі посилання та побудувати нові.


11
+1 - мені подобається ця відповідь - вона швидка, брудна, противна і дуже ефективна. Я використовував у сріблястому світлі та використовував DataContractSerializer, оскільки BinarySerializer був недоступний. Кому потрібно писати сторінки коду клонування об’єктів, коли ви просто можете це зробити? :)
Slugster

3
Мені це подобається. Хоча приємно робити речі «правильно», швидко і брудно часто стає в нагоді.
Одрад

3
Швидкий! але: Чому брудно?
рейзерле

2
Це глибокі клони і швидко і легко. Будьте уважні до інших пропозицій на цій сторінці. Я спробував кілька, і вони не глибоко клонуються.
RandallTo

2
Єдиний негативний аспект, якщо ви так можете його назвати, - це те, що ваші класи повинні бути позначені "Постійними", щоб це працювало.
Tuukka Haapaniemi

29

Для клонування списку просто зателефонуйте .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 }
> 

3
Найпростіший на сьогоднішній день рішення
curveorzos

28
Трохи попереджаючи, що це неглибока копія ... Це створить два об'єкти списку, але об'єкти всередині будуть однаковими. Тобто зміна одного властивості змінить той самий об’єкт / властивість у вихідному списку.
Марк Г

22

Після невеликої модифікації ви також можете клонувати:

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

Не забувайте, що T має бути серіалізаційним, інакше ви отримаєте System.Runtime.Serialization.SerializationException.
Бенс Вегерт

Хороша відповідь. Один натяк: Ви можете додати if (!obj.GetType().IsSerializable) return default(T);як перше твердження, яке запобігає виключенню. І якщо ви перейдете на метод розширення, ви навіть можете скористатися оператором Elvis типу var b = a?.DeepClone();(наведено, var a = new List<string>() { "a", "b" }; наприклад).
Метт

15

Якщо вам не потрібен фактичний клон кожного об'єкту всередині вашого List<T>, найкращий спосіб клонувати список - це створити новий список зі старим списком як параметр колекції.

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

Зміни, myListтакі як вставка або видалення, не вплинуть cloneOfMyListі навпаки.

Однак фактичні об'єкти, які містяться у двох Списках, все ще однакові.


Я погоджуюся з user49126, я бачу, що це неглибока копія, і зміни, внесені до одного списку, відображені в іншому списку.
Сейдлероні

1
@Seidleroni, ти помилився. Зміни, внесені до списку, змінюються в іншому списку, зміни в самому списку - не.
Веллінгтон Занеллі

Це неглибока копія.
Елліот Чен

Як це неглибока копія?
mko

2
@WellingtonZanelli Щойно підтвердив, що видалення елемента з myList також видаляє його з cloneOfMyList.
Нік

13

Використовувати AutoMapper (або будь-яку іншу карту), яку ви віддаєте перевагу, для клонування просто та багато ремонту.

Визначте своє відображення:

Mapper.CreateMap<YourType, YourType>();

Робіть магію:

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

13

Якщо ви дбаєте лише про типи значень ...

І ви знаєте тип:

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

15
Це не клонує елементів.
Джефф Йейтс

10

Якщо ви вже посилалися на Newtonsoft.Json у своєму проекті, а ваші об'єкти можна серіалізувати, ви завжди можете використовувати:

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

Можливо, це не найефективніший спосіб зробити це, але якщо ви це не зробите в 100 тисяч разів, ви навіть не помітите різницю швидкості.


4
Справа не в різниці швидкостей, а в читанні. Якби я прийшов до цього рядка коду, я б ляпнув головою і поцікавився, чому вони запровадили сторонню бібліотеку, щоб серіалізувати, а потім десеріалізувати об’єкт, який я б не знав, чому це відбувається. Крім того, це не працює для списку моделей з об'єктами, що мають кругову структуру.
Jonathon Cwik

1
Цей код відмінно працював для мене для глибокого клонування. Додаток мігрує панель документів з Dev в QA на Prod. Кожен об'єкт є пакетом з декількох об'єктів шаблону документа, і кожен документ, у свою чергу, складається із списку об'єктів абзацу. Цей код дозволив мені серіалізувати об’єкти .NET "source" та негайно десеріалізувати їх до нових "цільових" об'єктів, які потім зберігаються в базі даних SQL в іншому середовищі. Після тонни досліджень я знайшов багато речей, багато з яких було занадто громіздким, і вирішив спробувати це. Цей короткий і гнучкий підхід був "справедливим"!
Developer63

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

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

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

3

Ми з моїм другом Грегором Мартиновичем придумали це просте рішення за допомогою серіалізатора 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);
}

2
Приватні члени не клонуються методом JSON. stackoverflow.com/a/78612/885627
himanshupareek66

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

3

Мені пощастить, якщо хтось колись прочитає це ... але для того, щоб не повернути список об’єктів типу в моїх методах 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 (тоді як інтерфейс ICloneable хотів, щоб мій метод повернув список об’єктів):

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

Комусь подобається? Будь-які поліпшення?


2

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


Це не працює; зміни значень у клонованому масиві ВИНАГИ змінюють значення у вихідному списку.
Ящірка Бернуллі

ви можете використовувати var clonedList = ListOfStrings.ConvertAll (p => p); як дав @IbrarMumtaz .... Працює ефективно ... Зміни одного списку зберігаються в собі і не відображаються в іншому
zainul

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

Примітка: якщо ви внесете будь-які зміни на копію (або клонування), це не вплине на оригінальний об'єкт.


2

Якщо вам потрібен список клонованих з однаковою ємністю, ви можете спробувати це:

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

1

Я створив для себе деяке розширення, яке перетворює 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();
    }
}

Мабуть, деякі колекції (наприклад, DataGrid's SelectedItems at Silverlight) пропускають реалізацію CopyTo, що є проблемою при такому підході
Джордж Бірбіліс

1

Я використовую автоматизатор для копіювання об'єкта. Я просто налаштовую карту, яка відображає один об’єкт до себе. Ви можете завершити цю операцію будь-яким способом.

http://automapper.codeplex.com/


1

Використання акторів може бути корисним у цьому випадку для дрібної копії:

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

1

Для глибокої копії ICloneable є правильним рішенням, але ось аналогічний підхід до ICloneable, використовуючи конструктор замість інтерфейсу ICloneable.

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 робить це лаконічним та чистим. Так само ви можете зробити так, як пропонують інші відповіді, і зробити методи розширення тощо, але нічого з цього не потрібно.


Це називається "конструктор копій". Це підхід, схильний до помилок, кожного разу, коли ви додаєте нове поле до Student, ви повинні пам'ятати, щоб додати його до конструктора копій. Основна ідея "клону" - уникнути цієї проблеми.
kenno

2
Навіть із ICloneable, ви повинні мати метод "Clone" у своєму класі. Якщо ви не використовуєте рефлексію (яку ви також можете використовувати у вищевказаному підході), цей метод Clone буде виглядати реально схожим на підхід конструктора копіювання вище, і він буде страждати від тієї ж проблеми, що потребувати оновлення для нових / змінених полів. Але це говорить "Клас повинен бути оновлений, коли поля класу змінюються". Звичайно, це так;)
ztorstri

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

Інша річ: ви могли б використати роздуми. Якщо ви кешуєте це належним чином, то він клонуватиме 1000 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 - це лише мій тестовий клас, де я провів десяток тестів, включаючи використання вираження. Ви можете реалізувати це в іншій формі в розширенні чи будь-якому іншому. Не забувайте про кешування.

Я ще не перевіряв серіалізацію, але сумніваюся в поліпшенні з мільйоном класів. Я спробую щось швидке протобуф / ньютон.

PS: для простоти читання я тут використовував лише автоматичну властивість. Я міг би оновити програму FieldInfo, або вам слід легко її реалізувати самостійно.

Нещодавно я перевірив серіалізатор протокольних буферів із функцією DeepClone поза коробкою. Він виграє за 4,2 секунди на мільйоні простих предметів, але коли мова йде про внутрішні об’єкти, він виграє з результатом 7,4 секунди.

Serializer.DeepClone(personList);

РЕЗЮМЕ: Якщо у вас немає доступу до класів, це допоможе. Інакше це залежить від кількості об’єктів. Я думаю, ви могли б використовувати відображення до 10 000 об'єктів (можливо трохи менше), але для цього серіалізатор протокольних буферів працюватиме краще.


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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.