Jak sklonować listę generyczną w C#?

Mam ogólną listę obiektów w C# i chcę ją sklonować. Elementy z listy można klonować, ale nie ma opcji do wykonania list.Clone().

Czy jest jakiś łatwy sposób na obejście tego?
Author: Peter Mortensen, 2008-10-21

23 answers

Możesz użyć metody rozszerzenia.

static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}
 316
Author: ajm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-23 19:11:35

Jeśli twoje elementy są typami wartości, możesz po prostu zrobić:

List<YourType> newList = new List<YourType>(oldList);

Jeśli jednak są to typy referencyjne i chcesz mieć głęboką kopię (zakładając, że twoje elementy poprawnie zaimplementują ICloneable), możesz zrobić coś takiego:

List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);

oldList.ForEach((item) =>
    {
        newList.Add((ICloneable)item.Clone());
    });

Oczywiście, zastąp ICloneable w powyższych generikach i wrzuć dowolny typ elementu, który implementuje ICloneable.

Jeśli twój Typ elementu nie obsługuje ICloneable, ale ma Konstruktor kopiujący, możesz to zrobić zamiast:

List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);

oldList.ForEach((item)=>
    {
        newList.Add(new YourType(item));
    });

Osobiście unikałbym ICloneable ze względu na potrzebę zagwarantowania głębokiej kopii wszystkich członków. Zamiast tego sugerowałbym Konstruktor kopiujący lub metodę fabryczną, taką jak YourType.CopyFrom(YourType itemToCopy), która zwraca nową instancję YourType.

Każda z tych opcji może być opakowana metodą (rozszerzenie lub inaczej).

 422
Author: Jeff Yates,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-21 17:06:22
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;
}

Jest to jeden ze sposobów, aby to zrobić z C# i. NET 2.0. Twój obiekt wymaga [Serializable()]. Celem jest utrata wszelkich odniesień i budowanie nowych.

 72
Author: Patrick Desjardins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-09 13:21:47

Dla płytkiej kopii, możesz zamiast tego użyć metody GetRange klasy generic List.

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

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

Cytowane z: Generyki receptur

 61
Author: Anthony Potts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-07 14:15:37

Po drobnej modyfikacji można również sklonować:

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;
}
 19
Author: Ajith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-03 06:25:43

Jeśli nie potrzebujesz rzeczywistego klonu każdego pojedynczego obiektu wewnątrz List<T>, najlepszym sposobem klonowania listy jest utworzenie nowej listy ze starą listą jako parametrem kolekcji.

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

Zmiany na myList takie jak insert lub remove nie będą miały wpływu na cloneOfMyList i odwrotnie.

Rzeczywiste obiekty, które zawierają te dwie listy, są jednak nadal takie same.

 14
Author: Jader Feijo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-10 14:09:54

Użycie Automapp (lub dowolnej lib mapowania, którą wolisz) do klonowania jest proste i łatwe do utrzymania.

Zdefiniuj swoje odwzorowanie:

Mapper.CreateMap<YourType, YourType>();

Czyń magię:

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);
 13
Author: Derek Liang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-13 23:20:22

Aby sklonować listę wystarczy zadzwonić .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 }
> 
 13
Author: Xavier John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-25 00:35:03

Jeśli zależy Ci tylko na typach wartości...

I znasz typ:

List<int> newList = new List<int>(oldList);

Jeśli nie znasz wcześniej typu, będziesz potrzebował funkcji helpera:

List<T> Clone<T>(IEnumerable<T> oldList)
{
    return newList = new List<T>(oldList);
}

Sprawiedliwy:

List<string> myNewList = Clone(myOldList);
 12
Author: James Curran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-15 13:08:48

Jeśli odwołałeś się już do Newtonsoft.Json w Twoim projekcie i twoje obiekty są serializowalne zawsze możesz użyć:

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

Prawdopodobnie nie jest to najskuteczniejszy sposób, ale jeśli nie robisz tego 100 z 1000 razy, możesz nawet nie zauważyć różnicy prędkości.

 7
Author: ProfNimrod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-01 14:43:56
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
Author: pratik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-25 16:22:23
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
Author: Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-07 07:04:00
    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
Author: shahrooz.bazrafshan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-10 07:40:51

Można również po prostu przekonwertować listę na tablicę za pomocą ToArray, a następnie sklonować tablicę za pomocą Array.Clone(...). W zależności od twoich potrzeb, metody zawarte w klasie Array mogą zaspokoić Twoje potrzeby.

 2
Author: JHaps,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-15 15:34:44

Możesz użyć metody rozszerzenia:

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

}

Możesz klonować wszystkie obiekty używając ich wartości typu members na przykład, rozważ tę klasę:

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

Uwaga: Jeśli dokonasz jakiejkolwiek zmiany podczas kopiowania (lub klonowania), nie wpłynie to na oryginalny obiekt.

 2
Author: user2463322,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-26 09:08:21

Jeśli potrzebujesz sklonowanej listy o tej samej pojemności, możesz spróbować tego:

public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}
 2
Author: user3245269,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-16 12:02:38

Mój przyjaciel Gregor Martinovic i ja wymyśliliśmy to łatwe rozwiązanie przy użyciu JavaScript Serializer. Nie ma potrzeby, aby oznaczyć klasy jako Serializowalne i w naszych testach przy użyciu Newtonsoft JsonSerializer nawet szybciej niż przy użyciu BinaryFormatter. Z metodami rozszerzeń używanymi na każdym obiekcie.

Standardowa opcja. Net JavascriptSerializer:

public static T DeepCopy<T>(this T value)
{
    JavaScriptSerializer js = new JavaScriptSerializer();

    string json = js.Serialize(value);

    return js.Deserialize<T>(json);
}

Faster option using Newtonsoft JSON :

public static T DeepCopy<T>(this T value)
{
    string json = JsonConvert.SerializeObject(value);

    return JsonConvert.DeserializeObject<T>(json);
}
 2
Author: F.H.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-16 12:22:51

Zrobiłem dla mojego własnego rozszerzenia, które konwertuje kolekcję elementów, które nie implementują 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();
    }
}
 1
Author: wudzik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-03 12:41:06

Używam automapper do kopiowania obiektu. Właśnie ustawiłem mapowanie, które mapuje jeden obiekt do siebie. Możesz owinąć tę operację, jak chcesz.

Http://automapper.codeplex.com/

 1
Author: Dan H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-13 14:28:08

Poniższy kod powinien zostać przeniesiony na listę z minimalnymi zmianami.

Zasadniczo działa poprzez wstawianie nowej liczby losowej z większego zakresu z każdą kolejną pętlą. Jeśli istnieją już liczby, które są takie same lub wyższe od niego, przesuń te liczby losowe o jeden, aby przenieść je do nowego większego zakresu indeksów losowych.

// 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
Author: Adam Lewis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-16 11:56:03

Inna sprawa: przydałoby się odbicie. Jeśli pamięć podręczna będzie prawidłowo, to sklonuje 1 000 000 obiektów w ciągu 5,6 sekundy (niestety, 16,4 sekundy z obiektami wewnętrznymi).

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

Zmierzyłem to w prosty sposób, używając klasy 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;

Wynik: z wewnętrznym obiektem PersonInstance - 16.4, PersonInstance = null-5.6

CopyFactory to tylko moja klasa testowa, gdzie mam kilkanaście testów, w tym użycie wyrażenia. Możesz to zaimplementować w innej formie w przedłużeniu czy jakoś tak. Nie zapomnij o buforowaniu.

Nie testowałem jeszcze serializacji, ale wątpię w poprawę z milionem klas. Spróbuję czegoś szybkiego protobuf / newton.

P. S.: ze względu na prostotę czytania, użyłem tutaj tylko auto-własności. Mogę zaktualizować FieldInfo, lub powinieneś łatwo zaimplementować to samodzielnie.

Ostatnio przetestowałem bufory protokołu serializer z funkcją DeepClone po wyjęciu z pudełka. Wygrywa z 4,2 sekund na milionie prostych obiektów, ale jeśli chodzi o obiekty wewnętrzne, wygrywa z wynikiem 7,4 sekundy.

Serializer.DeepClone(personList);

Podsumowanie: Jeśli nie masz dostępu do klas, to pomoże. W przeciwnym razie zależy to od liczby obiektów. Myślę, że przydałoby się odbicie do 10 000 obiektów (może trochę mniej), ale za więcej niż to bufory protokołu serializer sprawdzą się lepiej.

 0
Author: Roma Borodov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-16 12:02:11

Istnieje prosty sposób na klonowanie obiektów w C# przy użyciu serializera JSON i deserializera.

Możesz utworzyć klasę rozszerzenia:

using Newtonsoft.Json;

static class typeExtensions
{
    [Extension()]
    public static T jsonCloneObject<T>(T source)
    {
    string json = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(json);
    }
}

Do klonowania i obiektowania:

obj clonedObj = originalObj.jsonCloneObject;
 0
Author: Albert arnau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-16 12:23:34
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();
 0
Author: Steve,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-18 04:58:56