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?
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();
}
}
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).
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.
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
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;
}
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.
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>);
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 }
>
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);
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.
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;
}
}
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.
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.
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;
}
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);
}
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();
}
}
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.
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;
}
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.
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;
//try this
List<string> ListCopy= new List<string>(OldList);
//or try
List<T> ListCopy=OldList.ToList();