Comment cloner une liste générique en C#?

j'ai une liste générique d'objets en C#, et je souhaite cloner la liste. Les éléments dans la liste sont clonables, mais il ne semble pas y avoir une option pour faire list.Clone() .

Est-il un moyen facile de contourner cela?

472
demandé sur Peter Mortensen 2008-10-21 20:47:05

23 réponses

Vous pouvez utiliser une méthode d'extension.

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
répondu ajm 2013-09-23 19:11:35

si vos éléments sont des types de valeur, alors vous pouvez juste faire:

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

cependant, si ce sont des types de référence et que vous voulez une copie profonde (en supposant que vos éléments implémentent correctement ICloneable ), vous pouvez faire quelque chose comme ceci:

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

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

évidemment, remplacez ICloneable dans les génériques ci-dessus et moulez avec n'importe quel type d'élément qui implémente ICloneable .

si votre type d'élément ne supporte pas ICloneable mais a un copy-constructor, vous pouvez faire ceci à la place:

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

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

personnellement, j'éviterais ICloneable à cause de la nécessité de garantir une copie profonde de tous les membres. Au lieu de cela, je suggérerais le copy-constructor ou une méthode d'usine comme YourType.CopyFrom(YourType itemToCopy) qui renvoie une nouvelle instance de YourType .

L'une de ces options peut être enveloppée par une méthode (extension ou autre).

422
répondu Jeff Yates 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;
}

C'est une façon de le faire avec C# et .NET 2.0. Votre objet doit être [Serializable()] . L'objectif est de perdre toutes les références et d'en construire de nouveaux.

72
répondu Patrick Desjardins 2015-07-09 13:21:47

pour une copie superficielle, vous pouvez utiliser la méthode GetRange de la classe de liste générique.

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

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

Cité de: recettes génériques

61
répondu Anthony Potts 2018-04-07 14:15:37

après une légère modification, vous pouvez aussi cloner:

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
répondu Ajith 2012-12-03 06:25:43

sauf si vous avez besoin d'un clone réel de chaque objet dans votre List<T> , la meilleure façon de cloner une liste est de créer une nouvelle liste avec l'ancienne liste comme paramètre de collecte.

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

les changements à myList tels que insérer ou supprimer n'affecteront pas cloneOfMyList et vice versa.

Les objets réels les deux Listes contiennent sont toujours les mêmes.

14
répondu Jader Feijo 2015-07-10 14:09:54

utiliser AutoMapper (ou tout autre LIB de mapping que vous préférez) pour cloner est simple et beaucoup maintenable.

Définissez votre cartographie:

Mapper.CreateMap<YourType, YourType>();

Faire de la magie:

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);
13
répondu Derek Liang 2013-02-13 23:20:22

pour cloner une liste, appelez .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
répondu Xavier John 2017-09-25 00:35:03

si vous ne vous souciez que des types de valeurs...

et vous savez le type:

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

si vous ne connaissez pas le type avant, vous aurez besoin d'une fonction helper:

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

Le juste:

List<string> myNewList = Clone(myOldList);
12
répondu James Curran 2013-04-15 13:08:48

si vous avez déjà fait référence à Newtonsoft.Json dans votre projet et vos objets sont sérialisables, vous pouvez toujours utiliser:

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

N'est peut-être pas la façon la plus efficace de le faire, mais à moins que vous ne le fassiez 100 fois sur 1000, vous ne remarquerez peut-être même pas la différence de vitesse.

7
répondu ProfNimrod 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
répondu pratik 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
répondu Peter 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
répondu shahrooz.bazrafshan 2016-04-10 07:40:51

vous pouvez aussi simplement convertir la liste en un tableau en utilisant ToArray , puis cloner le tableau en utilisant Array.Clone(...) . Selon vos besoins, les méthodes incluses dans la classe Array pourrait répondre à vos besoins.

2
répondu JHaps 2015-01-15 15:34:44

vous pouvez utiliser la méthode d'extension:

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

}

vous pouvez cloner tous les objets en utilisant leur type de valeur members par exemple, considérez cette classe:

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

Note: Si vous faites un changement sur la copie (ou le clone) il n'affectera pas l'objet original.

2
répondu user2463322 2016-09-26 09:08:21

si vous avez besoin d'une liste clonée avec la même capacité, vous pouvez essayer ceci:

public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}
2
répondu user3245269 2017-05-16 12:02:38

mon ami Gregor Martinovic et moi avons trouvé cette solution facile en utilisant un sérialiseur JavaScript. Il n'est pas nécessaire de marquer les classes comme étant sérialisables et dans nos tests utilisant le Newtonsoft JsonSerializer encore plus rapidement que L'utilisation de BinaryFormatter. Avec des méthodes d'extension utilisables sur chaque objet.

Standard. net option JavascriptSerializer:

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

    string json = js.Serialize(value);

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

option plus rapide en utilisant Newtonsoft JSON :

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

    return JsonConvert.DeserializeObject<T>(json);
}
2
répondu F.H. 2017-05-16 12:22:51

j'ai fait pour mon propre quelque extension qui convertit ICollection D'articles qui ne mettent pas en œuvre 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
répondu wudzik 2013-07-03 12:41:06

j'utilise automapper pour copier un objet. Je viens de configurer un mappage de cartes un objet à lui-même. Vous pouvez boucler cette opération comme vous voulez.

http://automapper.codeplex.com /

1
répondu Dan H 2014-10-13 14:28:08

le code suivant doit être transféré sur une liste avec des modifications minimales.

fondamentalement, cela fonctionne en insérant un nouveau nombre aléatoire à partir d'une plus grande gamme avec chaque boucle successive. S'il existe déjà des nombres qui sont les mêmes ou plus élevés qu'elle, déplacez ces nombres aléatoires vers le haut un de sorte qu'ils transfèrent dans la nouvelle plus grande gamme d'indices aléatoires.

// 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
répondu Adam Lewis 2017-05-16 11:56:03

autre chose: vous pourriez utiliser la réflexion. Si vous le cachez correctement, il clonera 1 000 000 d'objets en 5,6 secondes (malheureusement, 16,4 secondes avec des objets internes).

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

Je l'ai mesuré d'une manière simple, en utilisant la classe des observateurs.

 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;

résultat: avec L'objet intérieur PersonInstance-16.4, PersonInstance = null-5.6

CopyFactory est juste ma classe de test où j'ai une douzaine de tests incluant l'usage de l'expression. Vous pouvez mettre en œuvre ceci sous une autre forme dans une extension ou autre. N'oubliez pas de mise en cache.

Je n'ai pas encore testé la sérialisation, mais je doute d'une amélioration avec un million de classes. Je vais essayer quelque chose de rapide protobuf/newton.

P. S.: Par souci de simplicité, je n'ai utilisé ici que l'auto-propriété. Je pourrais mettre à jour avec FieldInfo, ou vous devriez facilement mettre en œuvre cela par vos propres.

j'ai récemment testé le protocole tampons serializer avec la fonction DeepClone de la boîte. Il gagne 4,2 secondes, sur un million d'objets simples, mais quand il vient à l'intérieur des objets, il gagne le résultat 7,4 secondes.

Serializer.DeepClone(personList);

RÉSUMÉ: Si vous n'avez pas accès aux classes, alors ce sera l'aider. Sinon, il dépend du nombre d'objets. Je pense que vous pourriez utiliser la réflexion jusqu'à 10 000 objets (peut-être un peu moins), mais pour plus que cela le serializer de tampons de protocole fonctionnera mieux.

0
répondu Roma Borodov 2017-05-16 12:02:11

il y a une façon simple de cloner des objets en C# en utilisant un serializer et un deserializer JSON.

vous pouvez créer une classe d'extension:

using Newtonsoft.Json;

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

à cloner et À l'objet:

obj clonedObj = originalObj.jsonCloneObject;
0
répondu Albert arnau 2017-05-16 12:23:34
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();
0
répondu Steve 2018-02-18 04:58:56