web-dev-qa-db-fra.com

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

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

Y a-t-il un moyen facile de contourner cela?

497
Fiona

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

Si vos éléments sont des types valeur, alors vous pouvez simplement faire:

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

Cependant, s’il s’agit de types de référence et que vous souhaitez une copie complète (en supposant que vos éléments implémentent correctement ICloneable), vous pouvez procéder comme suit:

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 utilisez n'importe quel type d'élément implémentant ICloneable.

Si votre type d'élément ne prend pas en charge ICloneable mais dispose d'un constructeur de copie, vous pouvez le faire à 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 en raison de la nécessité de garantir une copie conforme de tous les membres. Au lieu de cela, je suggérerais le constructeur de copie ou une méthode d'usine telle que YourType.CopyFrom(YourType itemToCopy) qui renvoie une nouvelle instance de YourType.

Chacune de ces options peut être encapsulée par une méthode (extension ou autre).

441
Jeff Yates
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 nécessite d'être [Serializable()]. L'objectif est de perdre toutes les références et d'en créer de nouvelles.

77
Patrick Desjardins

Pour une copie superficielle, vous pouvez utiliser à la place la méthode GetRange de la classe List 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

71
Anthony Potts

Après une légère modification, vous pouvez également 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;
}
21
Ajith

Sauf si vous avez besoin d'un clone de chaque objet dans votre List<T>, le meilleur moyen de cloner une liste est de créer une nouvelle liste avec l'ancienne liste comme paramètre de collection.

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

Les modifications apportées à myList telles que insérer ou supprimer n'affecteront pas cloneOfMyList et vice versa.

Les objets contenus dans les deux listes sont toutefois les mêmes.

15
Jader Feijo

Pour cloner une liste, appelez simplement .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 }
> 
15
Xavier John

Si vous ne vous souciez que des types de valeur ...

Et vous connaissez le type:

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

Si vous ne connaissez pas le type auparavant, vous aurez besoin d'une fonction d'assistance:

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

Le juste:

List<string> myNewList = Clone(myOldList);
13
James Curran

Utiliser AutoMapper (ou la librairie de mapping que vous préférez) pour cloner est simple et facile à gérer.

Définissez votre mapping:

Mapper.CreateMap<YourType, YourType>();

Faites la magie:

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

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

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

Ce n’est peut-être pas le moyen le plus efficace de le faire, mais à moins que vous ne le fassiez des centaines de milliers de fois, vous ne remarquerez peut-être même pas la différence de vitesse.

9
ProfNimrod
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
pratik
    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
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
Peter

Vous pouvez également simplement convertir la liste en un tableau à l'aide de ToArray, puis le cloner à l'aide de Array.Clone(...). Selon vos besoins, les méthodes incluses dans la classe Array peuvent répondre à vos besoins.

2
JHaps

Mon ami Gregor Martinovic et moi avons proposé cette solution simple à l’aide d’un sérialiseur JavaScript. Il n'est pas nécessaire de marquer les classes comme étant Serializable et dans nos tests utilisant Newtonsoft JsonSerializer encore plus rapidement que d'utiliser BinaryFormatter. Avec des méthodes d'extension utilisables sur chaque objet.

Option standard .NET 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 utilisant Newtonsoft JSON :

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

    return JsonConvert.DeserializeObject<T>(json);
}
2
F.H.

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
user3245269

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 leurs membres de type valeur, par exemple, considérons 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 
    }
};

Remarque: si vous modifiez la copie (ou le clonage), cela n'affectera pas l'objet d'origine.

2
user2463322

J'ai créé une extension qui convertit ICollection d'éléments qui ne sont pas implémentés.

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
wudzik

L'utilisation d'un casting peut être utile, dans ce cas, pour une copie superficielle:

IList CloneList(IList list)
{
    IList result;
    result = (IList)Activator.CreateInstance(list.GetType());
    foreach (object item in list) result.Add(item);
    return result;
}

appliqué à la liste générique:

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);
1
Thomas Cerny

J'utilise automapper pour copier un objet. Je viens d'installer une cartographie qui mappe un objet sur lui-même. Vous pouvez effectuer cette opération comme bon vous semble. 

http://automapper.codeplex.com/

1
Dan H

Une autre chose: vous pouvez utiliser la réflexion. Si vous mettez cela correctement en cache, alors il va cloner 1 000 000 objets en 5,6 secondes (malheureusement, 16,4 secondes avec les 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é de manière simple, en utilisant la classe 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;

RESULT: Avec l'objet interne PersonInstance - 16.4, PersonInstance = null - 5.6

CopyFactory est juste ma classe de test où j'ai une douzaine de tests, y compris l'utilisation de l'expression. Vous pouvez implémenter cela sous une autre forme dans une extension ou autre. N'oubliez pas de mettre 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é de lecture, je n’ai utilisé ici que la propriété automatique. Je pourrais mettre à jour avec FieldInfo, ou vous devriez facilement l'implémenter vous-même.

J'ai récemment testé le Protocol Buffers serializer avec la fonction DeepClone prête à l'emploi. Il gagne avec 4,2 secondes sur un million d'objets simples, mais lorsqu'il s'agit d'objets intérieurs, il gagne avec un résultat de 7,4 secondes.

Serializer.DeepClone(personList);

SUMMARY: Si vous n'avez pas accès aux classes, cela vous aidera. Sinon, cela dépend du nombre d'objets. Je pense que vous pouvez utiliser une réflexion allant jusqu'à 10 000 objets (peut-être un peu moins), mais si vous utilisez plus, le sérialiseur de tampons de protocole fonctionnera mieux.

0
Roma Borodov

ICloneable est la solution correcte pour une copie en profondeur, mais voici une approche similaire à ICloneable en utilisant le constructeur à la place de l'interface 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();

vous aurez besoin de la bibliothèque suivante où vous faites la copie

using System.Linq

vous pouvez également utiliser une boucle for à la place de System.Linq, mais Linq la rend concise et propre. De même, vous pouvez faire comme d'autres réponses l'ont suggéré et créer des méthodes d'extension, etc., mais rien de tout cela n'est nécessaire.

0
ztorstri

Je serai chanceux si quelqu'un lit jamais ceci ... mais afin de ne pas renvoyer de liste d'objets type dans mes méthodes Clone, j'ai créé une interface:

public interface IMyCloneable<T>
{
    T Clone();
}

Puis j'ai spécifié l'extension:

public static List<T> Clone<T>(this List<T> listToClone) where T : IMyCloneable<T>
{
    return listToClone.Select(item => (T)item.Clone()).ToList();
}

Et voici une implémentation de l'interface dans mon logiciel de marquage A/V. Je voulais que ma méthode Clone () renvoie une liste de VidMark (alors que l'interface ICloneable voulait que ma méthode renvoie une liste d'objets):

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

Et enfin, l'utilisation de l'extension dans une classe:

private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;

//Other methods instantiate and fill the lists

private void SetUndoVidMarks()
{
    _UndoVidMarks = _VidMarks.Clone();
}

Quelqu'un l'aime? Des améliorations?

0
John Kurtz

Il existe un moyen simple de cloner des objets en C # à l'aide d'un sérialiseur et d'un désérialiseur 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);
    }
}

Pour cloner et objecter:

obj clonedObj = originalObj.jsonCloneObject;
0
Albert arnau
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();
0
Steve

Le code suivant devrait être transféré sur une liste avec des modifications minimes. 

Fondamentalement, cela fonctionne en insérant un nouveau nombre aléatoire dans une plage plus grande à chaque boucle successive. S'il existe déjà des nombres identiques ou supérieurs à ceux-ci, déplacez ces nombres aléatoires vers le haut afin qu'ils soient transférés dans la nouvelle gamme plus large d'index 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
Adam Lewis