Domanda

Ho un elenco generico di oggetti in C # e desidero clonare l'elenco. Gli elementi all'interno dell'elenco sono clonabili, ma non sembra esserci un'opzione per fare list.Clone().

C'è un modo semplice per aggirare questo?

È stato utile?

Soluzione

Puoi usare un metodo di estensione.

static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}

Altri suggerimenti

Se i tuoi elementi sono tipi di valore, puoi semplicemente fare:

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

Tuttavia, se sono tipi di riferimento e desideri una copia approfondita (supponendo che i tuoi elementi implementino correttamente ICloneable), potresti fare qualcosa del genere:

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

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

Ovviamente, sostituisci YourType.CopyFrom(YourType itemToCopy) nelle generiche sopra e esegui il cast con qualunque sia il tuo tipo di elemento che implementa YourType.

Se il tuo tipo di elemento non supporta <=> ma ha un costruttore di copia, puoi invece farlo:

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

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

Personalmente, eviterei <=> a causa della necessità di garantire una copia approfondita di tutti i membri. Invece, suggerirei il costruttore di copia o un metodo factory come <=> che restituisce una nuova istanza di <=>.

Ognuna di queste opzioni potrebbe essere racchiusa in un metodo (estensione o altro).

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

Questo è un modo per farlo con C # e .NET 2.0. Il tuo oggetto deve essere [Serializable()]. L'obiettivo è perdere tutti i riferimenti e costruirne di nuovi.

Per una copia superficiale, puoi invece utilizzare il metodo GetRange della classe List generica.

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

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

Citato da: Ricette Generics

Dopo una leggera modifica puoi anche clonare:

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

Per clonare un elenco basta chiamare .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 }
> 

A meno che non sia necessario un clone effettivo di ogni singolo oggetto all'interno del List<T>, il modo migliore per clonare un elenco è quello di creare un nuovo elenco con il vecchio elenco come parametro di raccolta.

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

Le modifiche a myList come l'inserimento o la rimozione non influiranno su cloneOfMyList e viceversa.

Gli oggetti reali contenuti nelle due liste sono comunque gli stessi.

Usa AutoMapper (o qualunque libreria di mapping tu preferisca) per clonare è semplice e molto gestibile.

Definisci il tuo mapping:

Mapper.CreateMap<YourType, YourType>();

Fai la magia:

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

Se ti interessa solo i tipi di valore ...

E conosci il tipo:

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

Se non conosci il tipo prima, avrai bisogno di una funzione di supporto:

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

Il giusto:

List<string> myNewList = Clone(myOldList);

Se hai già fatto riferimento a Newtonsoft.Json nel tuo progetto e i tuoi oggetti sono serializzabili, puoi sempre usare:

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

Forse non è il modo più efficiente per farlo, ma a meno che tu non lo faccia centinaia di migliaia di volte potresti non notare nemmeno la differenza di velocità.

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

Potresti anche semplicemente convertire l'elenco in un array usando ToArray, e quindi clonare l'array usando Array.Clone(...). A seconda delle esigenze, i metodi inclusi nella classe Array potrebbero soddisfare le tue esigenze.

Puoi usare il metodo di estensione:

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

}

Puoi clonare tutti gli oggetti usando, ad esempio, i loro membri del tipo di valore, considera questa 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 
    }
};

Nota: se si apportano modifiche alla copia (o al clone), ciò non influirà sull'oggetto originale.

Se hai bisogno di un elenco clonato con la stessa capacità, puoi provare questo:

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

Il mio amico Gregor Martinovic e io abbiamo trovato questa semplice soluzione usando un serializzatore JavaScript. Non è necessario contrassegnare le classi come serializzabili e nei nostri test utilizzando Newtonsoft JsonSerializer anche più velocemente rispetto all'utilizzo di BinaryFormatter. Con metodi di estensione utilizzabili su ogni oggetto.

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

Opzione più veloce utilizzando Newtonsoft JSON :

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

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

Ho creato una mia estensione che converte ICollection di articoli che non implementano 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();
    }
}

Uso automapper per copiare un oggetto. Ho appena impostato una mappatura che mappa un oggetto su se stesso. Puoi concludere questa operazione come preferisci.

http://automapper.codeplex.com/

L'uso di un cast può essere utile, in questo caso, per una copia superficiale:

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

applicato all'elenco generico:

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);

Il codice seguente dovrebbe essere trasferito in un elenco con modifiche minime.

Fondamentalmente funziona inserendo un nuovo numero casuale da un intervallo maggiore con ogni ciclo successivo. Se esistono già numeri uguali o superiori, sposta i numeri casuali di uno in modo da trasferirli nella nuova gamma più ampia di indici casuali.

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

Un'altra cosa: potresti usare la riflessione. Se lo memorizzi correttamente nella cache, clonerà 1.000.000 di oggetti in 5,6 secondi (purtroppo, 16,4 secondi con oggetti interni).

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

L'ho misurato in modo semplice, usando 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;

RISULTATO: Con l'oggetto interno PersonInstance - 16.4, PersonInstance = null - 5.6

CopyFactory è solo la mia classe di test in cui ho dozzine di test incluso l'uso dell'espressione. Potresti implementarlo in un'altra forma in un'estensione o altro. Non dimenticare la memorizzazione nella cache.

Non ho ancora testato la serializzazione, ma dubito di un miglioramento con un milione di classi. Proverò qualcosa di veloce protobuf / newton.

P.S .: per motivi di semplicità di lettura, ho usato solo la proprietà automatica qui. Potrei aggiornare con FieldInfo o dovresti implementarlo facilmente da solo.

Di recente ho testato il serializzatore Buffer di protocollo con la funzione DeepClone pronta all'uso. Vince con 4,2 secondi su un milione di oggetti semplici, ma quando si tratta di oggetti interni, vince con il risultato 7,4 secondi.

Serializer.DeepClone(personList);

SINTESI: se non hai accesso alle lezioni, ti sarà d'aiuto. Altrimenti dipende dal conteggio degli oggetti. Penso che potresti usare la riflessione fino a 10.000 oggetti (forse un po 'meno), ma per di più il serializzatore Protocol Buffers funzionerà meglio.

Esiste un modo semplice per clonare oggetti in C # usando un serializzatore e deserializzatore JSON.

Puoi creare una classe di estensione:

using Newtonsoft.Json;

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

Per clonare e obiettare:

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

Sarò fortunato se qualcuno leggerà mai questo ... ma per non restituire un elenco di oggetti tipo nei miei metodi Clone, ho creato un'interfaccia:

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

Quindi ho specificato l'estensione:

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

Ed ecco un'implementazione dell'interfaccia nel mio software di marcatura A / V. Volevo che il mio metodo Clone () restituisse un elenco di VidMark (mentre l'interfaccia ICloneable voleva che il mio metodo restituisse un elenco di oggetti):

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

E infine l'uso dell'estensione all'interno di una classe:

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

//Other methods instantiate and fill the lists

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

Qualcuno piace? Qualche miglioramento?

Per una copia approfondita, ICloneable è la soluzione corretta, ma ecco un approccio simile a ICloneable usando il costruttore invece dell'interfaccia 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();

avrai bisogno della seguente libreria in cui fai la copia

using System.Linq

puoi anche usare un ciclo for invece di System.Linq, ma Linq lo rende conciso e pulito. Allo stesso modo potresti fare come suggerito da altre risposte e creare metodi di estensione, ecc., Ma nulla di tutto ciò è necessario.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top