web-dev-qa-db-de.com

Wie kann ich eine generische Liste in C # klonen?

Ich habe eine generische Liste von Objekten in C # und möchte die Liste klonen. Die Elemente in der Liste sind klonbar, aber es scheint keine Option für list.Clone() zu geben.

Gibt es da einen einfachen Weg?

497
Fiona

Sie können eine Erweiterungsmethode verwenden.

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

Wenn Ihre Elemente Werttypen sind, können Sie Folgendes tun:

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

Wenn es sich jedoch um Referenztypen handelt und Sie eine tiefe Kopie wünschen (vorausgesetzt, Ihre Elemente implementieren ICloneable ordnungsgemäß), können Sie Folgendes tun:

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

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

Ersetzen Sie offensichtlich ICloneable in den oben genannten Generics und setzen Sie sie mit dem Elementtyp um, der ICloneable implementiert.

Wenn Ihr Elementtyp ICloneable nicht unterstützt, jedoch einen Kopierkonstruktor enthält, können Sie stattdessen Folgendes tun:

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

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

Persönlich würde ich ICloneable vermeiden, da eine tiefe Kopie aller Mitglieder garantiert werden muss. Stattdessen würde ich den Copy-Konstruktor oder eine Factory-Methode wie YourType.CopyFrom(YourType itemToCopy) vorschlagen, die eine neue Instanz von YourType zurückgibt.

Jede dieser Optionen kann von einer Methode (Erweiterung oder auf andere Weise) umschlossen werden.

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

Dies ist eine Möglichkeit, dies mit C # und .NET 2.0 zu tun. Ihr Objekt muss [Serializable()] sein. Das Ziel ist es, alle Referenzen zu verlieren und neue zu bauen.

77

Für eine flache Kopie können Sie stattdessen die GetRange-Methode der generischen List-Klasse verwenden.

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

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

Zitiert aus: Generics Rezepte

71
Anthony Potts

Nach einer geringfügigen Änderung können Sie auch klonen:

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

Wenn Sie keinen tatsächlichen Klon jedes einzelnen Objekts in Ihrem List<T> benötigen, können Sie eine Liste am besten klonen, indem Sie eine neue Liste mit der alten Liste als Erfassungsparameter erstellen.

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

Änderungen an myList, wie Einfügen oder Entfernen, wirken sich nicht auf cloneOfMyList aus und umgekehrt.

Die eigentlichen Objekte, die die beiden Listen enthalten, sind jedoch immer noch dieselben.

15
Jader Feijo

Um eine Liste zu klonen, rufen Sie .ToList () auf.

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

Wenn Sie sich nur für Werttypen interessieren ...

Und Sie kennen den Typ:

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

Wenn Sie den Typ noch nicht kennen, benötigen Sie eine Hilfsfunktion:

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

Die gerade:

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

Verwenden Sie AutoMapper (oder was auch immer Sie für die Zuordnungsbibliothek bevorzugen), um das Klonen zu vereinfachen.

Definieren Sie Ihr Mapping:

Mapper.CreateMap<YourType, YourType>();

Mach die Magie:

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

Wenn Sie in Ihrem Projekt bereits auf Newtonsoft.Json referenziert haben und Ihre Objekte serialisierbar sind, können Sie immer Folgendes verwenden:

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

Möglicherweise nicht der effizienteste Weg, um es zu tun, aber wenn Sie es nicht Hunderte von Tausenden von Malen machen, bemerken Sie möglicherweise nicht einmal den Geschwindigkeitsunterschied.

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

Sie können die Liste auch einfach mit ToArray in ein Array konvertieren und dann das Array mit Array.Clone(...)..__ klonen. Je nach Ihren Anforderungen können die in der Array-Klasse enthaltenen Methoden Ihren Anforderungen entsprechen.

2
JHaps

Mein Freund Gregor Martinovic und ich haben diese einfache Lösung mit einem JavaScript-Serializer entwickelt. Es ist nicht erforderlich, Klassen als serialisierbar zu kennzeichnen und in unseren Tests mit dem Newtonsoft JsonSerializer noch schneller als mit BinaryFormatter. Mit Erweiterungsmethoden, die für jedes Objekt verwendet werden können.

Standard .NET JavascriptSerializer Option:

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

    string json = js.Serialize(value);

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

Schnellere Option mit Newtonsoft JSON :

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

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

Wenn Sie eine geklonte Liste mit derselben Kapazität benötigen, können Sie Folgendes versuchen:

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

Sie können die Erweiterungsmethode verwenden: 

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

}

Sie können alle Objekte klonen, indem Sie beispielsweise deren Werttyp-Member verwenden. Beachten Sie diese Klasse: 

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

Hinweis: Wenn Sie beim Kopieren (oder Klonen) Änderungen vornehmen, hat dies keine Auswirkungen auf das ursprüngliche Objekt.

2
user2463322

Ich habe für mich selbst einige Erweiterungen erstellt, die ICollection von Elementen konvertieren, die IClonable nicht implementieren

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

In diesem Fall kann die Verwendung eines Casts für eine flache Kopie hilfreich sein:

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

auf generische Liste angewendet:

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

Ich verwende Automapper, um ein Objekt zu kopieren. Ich habe nur ein Mapping eingerichtet, das sich ein Objekt zuordnet. Sie können diese Operation beliebig wickeln. 

http://automapper.codeplex.com/

1
Dan H

Eine andere Sache: Sie könnten Reflektion verwenden. Wenn Sie dies ordnungsgemäß zwischenspeichern, werden 1.000.000 Objekte in 5,6 Sekunden (leider 16,4 Sekunden bei inneren Objekten) geklont.

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

Ich habe es auf einfache Weise mit der Watcher-Klasse gemessen.

 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: Mit innerem Objekt PersonInstance - 16.4, PersonInstance = null - 5.6

CopyFactory ist nur meine Testklasse, in der ich Dutzende von Tests einschließlich der Verwendung von Ausdrücken habe. Sie können dies in einer anderen Form in einer Erweiterung oder in etwas anderem implementieren. Vergiss das Zwischenspeichern nicht.

Ich habe die Serialisierung noch nicht getestet, bezweifle aber eine Verbesserung um eine Million Klassen. Ich versuche etwas schnelles Protobuf/Newton.

PS .: Der Einfachheit halber habe ich hier nur Auto-Property verwendet. Ich könnte ein Update mit FieldInfo durchführen, oder Sie sollten dies einfach selbst implementieren.

Vor kurzem habe ich den Serializer Protocol Buffers mit der DeepClone-Funktion getestet. Bei einer Million einfacher Objekte gewinnt es mit 4,2 Sekunden, bei inneren Objekten jedoch mit 7,4 Sekunden.

Serializer.DeepClone(personList);

SUMMARY: Wenn Sie keinen Zugriff auf die Klassen haben, wird dies hilfreich sein. Ansonsten hängt es von der Anzahl der Objekte ab. Ich denke, Sie könnten Reflection mit bis zu 10.000 Objekten verwenden (vielleicht ein bisschen weniger), aber darüber hinaus wird der Protokollpuffer-Serializer besser funktionieren.

0
Roma Borodov

Für eine tiefe Kopie ist ICloneable die richtige Lösung, aber hier ist ein ähnlicher Ansatz wie bei ICloneable, bei dem der Konstruktor anstelle der ICloneable-Schnittstelle verwendet wird.

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

sie benötigen die folgende Bibliothek, in der Sie die Kopie erstellen

using System.Linq

sie könnten auch eine for-Schleife anstelle von System.Linq verwenden, aber Linq macht es kurz und bündig. Ebenso könnten Sie tun, wie andere Antworten vorgeschlagen haben, und Erweiterungsmethoden usw. vornehmen, aber nichts davon ist notwendig.

0
ztorstri

Ich bin froh, wenn irgendjemand dies liest. Um jedoch keine Liste mit Typobjekt in meinen Clone-Methoden zurückzugeben, habe ich eine Schnittstelle erstellt:

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

Dann habe ich die Erweiterung angegeben:

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

Und hier ist eine Implementierung der Schnittstelle in meiner A/V-Markierungssoftware. Ich wollte, dass meine Clone () - Methode eine Liste von VidMark zurückgibt (während die ICloneable-Schnittstelle meine Methode eine Liste mit Objekten zurückgeben wollte):

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

Und schließlich die Verwendung der Erweiterung innerhalb einer Klasse:

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

//Other methods instantiate and fill the lists

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

Jemand mag es? Irgendwelche Verbesserungen?

0
John Kurtz

Es gibt eine einfache Möglichkeit, Objekte in C # mithilfe eines JSON-Serialisierers und Deserializers zu klonen.

Sie können eine Erweiterungsklasse erstellen:

using Newtonsoft.Json;

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

Klonen und Objekt:

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

Der folgende Code sollte mit minimalen Änderungen in eine Liste übernommen werden. 

Im Grunde funktioniert es, indem bei jeder nachfolgenden Schleife eine neue Zufallszahl aus einem größeren Bereich eingefügt wird. Wenn bereits Zahlen vorhanden sind, die gleich oder höher sind, verschieben Sie diese Zufallszahlen um eins, sodass sie in den neuen, größeren Bereich von Zufallsindizes übertragen werden.

// 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