Как мне клонировать общий список в C #? - PullRequest
518 голосов
/ 21 октября 2008

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

Есть ли простой способ обойти это?

Ответы [ 26 ]

0 голосов
/ 14 мая 2019

Для глубокой копии 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 делает его лаконичным и чистым. Точно так же вы могли бы сделать, как предлагали другие ответы, и сделать методы расширения и т. Д., Но ничего из этого не требуется.

0 голосов
/ 21 января 2019

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

А вот реализация интерфейса в моем программном обеспечении для маркировки аудио / видео. Я хотел, чтобы мой метод 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();
}

Кто-нибудь любит это? Какие-нибудь улучшения?

0 голосов
/ 18 февраля 2018
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();
0 голосов
/ 20 января 2017

Существует простой способ клонирования объектов в 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 голосов
/ 19 декабря 2015

Другое дело: вы могли бы использовать отражение. Если вы кешируете это правильно, то он клонирует 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 .: ради простоты чтения я использовал здесь только авто-свойства. Я мог бы обновить с FieldInfo, или вы должны легко реализовать это самостоятельно.

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

Serializer.DeepClone(personList);

РЕЗЮМЕ: Если у вас нет доступа к классам, то это поможет. В противном случае это зависит от количества объектов. Я думаю, что вы могли бы использовать отражение до 10000 объектов (может быть, немного меньше), но для большего, чем этот, сериализатор Protocol Buffers будет работать лучше.

0 голосов
/ 03 сентября 2015

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

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

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