web-dev-qa-db-ja.com

C#でジェネリックリストを複製する方法

私はC#のオブジェクトの一般的なリストを持っています、そしてリストをクローンしたいです。リスト内の項目は複製可能ですが、list.Clone()を実行するオプションはないようです。

これを回避する簡単な方法はありますか?

510
Fiona

拡張メソッドを使うことができます。

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

あなたの要素が値型なら、あなたはただすることができます:

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

ただし、それらが参照型であり、ディープコピーが必要な場合(要素にICloneableが正しく実装されていると仮定して)、次のようなことができます。

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

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

明らかに、上記の総称のICloneableを置き換えて、ICloneableを実装している要素型がなんであれキャストしてください。

あなたの要素型がICloneableをサポートしていないがコピーコンストラクタを持っている場合、代わりにこれを行うことができます:

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

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

個人的には、私はすべてのメンバーの深いコピーを保証する必要があるのでICloneableを避けます。代わりに、私はコピーコンストラクタかYourTypeの新しいインスタンスを返すYourType.CopyFrom(YourType itemToCopy)のようなファクトリメソッドを提案します。

これらのオプションはいずれもメソッド(拡張子など)でラップできます。

452
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#と.NET 2.0でそれをする一つの方法です。あなたのオブジェクトは[Serializable()]である必要があります。目標は、すべての参照を失い、新しいものを作成することです。

76

シャローコピーの場合は、代わりに汎用のListクラスのGetRangeメソッドを使用できます。

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

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

引用元: ジェネリックレシピ

75
Anthony Potts

ちょっとした修正の後、あなたはクローンすることもできます:

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

リストを複製するには、単に.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 }
> 
17
Xavier John

List<T>内のすべてのオブジェクトを実際に複製する必要がない限り、リストを複製する最善の方法は、古いリストをコレクションパラメータとして新しいリストを作成することです。

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

InsertやremoveのようなmyListの変更はcloneOfMyListに影響を与えません、そしてその逆も同様です。

2つのリストに含まれる実際のオブジェクトは、まだ同じです。

15
Jader Feijo

値型のみを気にしている場合.

そして、あなたはタイプを知っています:

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

あなたが前にタイプを知らないならば、あなたはヘルパー関数を必要とするでしょう:

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

ただ

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

AutoMapper(またはあなたが好むどんなマッピングライブラリでも)をクローンするのに使うのは簡単でたくさんのメンテナンス可能です。

マッピングを定義します。

Mapper.CreateMap<YourType, YourType>();

魔法をやる:

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

プロジェクトで既にNewtonsoft.Jsonを参照していて、オブジェクトが直列化可能な場合は、いつでも使用できます。

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

おそらくそれを行うための最も効率的な方法ではありませんが、あなたがそれをしているのでなければ何千回もの何千倍もの速度差に気付かないかもしれません。

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

ToArrayを使って単純にリストを配列に変換してから、Array.Clone(...)を使ってその配列を複製することもできます。ニーズに応じて、Arrayクラスに含まれているメソッドはニーズを満たすことができます。

2
JHaps

私の友人Gregor Martinovicと私はJavaScriptシリアライザを使ってこの簡単な解決策を思い付きました。 Newtonsoft JsonSerializerを使用して、BinaryFormatterを使用するよりも速く、クラスにSerializableとフラグを設定する必要はありません。すべてのオブジェクトで使用可能な拡張メソッド.

標準の.NET JavascriptSerializerオプション:

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

    string json = js.Serialize(value);

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

Newtonsoft JSONを使用した高速オプション

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

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

同じ容量のクローンリストが必要な場合は、これを試すことができます。

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

あなたは拡張方法を使用することができます:

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

}

たとえば、値型メンバーを使用してすべてのオブジェクトのクローンを作成できます。次のクラスを考えてください。

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

注意:コピー(またはクローン)を変更しても、元のオブジェクトには影響しません。

2
user2463322

私はIClonableを実装していないアイテムのICollectionを変換する私自身の拡張用に作りました

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

この場合は、キャストを使用すると便利です。

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

総称リストに適用:

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

オブジェクトをコピーするためにautomapperを使います。 1つのオブジェクトをそれ自体にマッピングするマッピングを設定しただけです。この操作は好きなようにラップできます。

http://automapper.codeplex.com/ /

1
Dan H

もう一つのこと:あなたは反射を使用することができます。これを正しくキャッシュすると、5.6秒で1,000,000個のオブジェクトのクローンが作成されます(残念ながら、内部オブジェクトで16.4秒)。

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

私はそれを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;

結果: 内部オブジェクトPersonInstance - 16.4、PersonInstance = null - 5.6

CopyFactoryは私のテストクラスで、式の使用を含む多数のテストがあります。あなたはこれをエクステンションの中の何か他の形で実装することができます。キャッシュを忘れないでください。

私はまだシリアライズをテストしていませんが、100万クラスの改善には疑問があります。速いプロトブフ/ニュートンを試してみます。

P.S。:読みやすくするために、ここではauto-propertyのみを使用しました。私はFieldInfoを使って更新することもできますし、あるいはあなたがこれをあなた自身で簡単に実装するべきです。

私は最近、 プロトコルバッファ シリアライザのDeepClone機能を箱から出してテストしました。 100万個の単純なオブジェクトに対して4.2秒で勝利しますが、内側のオブジェクトに関しては7.4秒で勝利します。

Serializer.DeepClone(personList);

要約: クラスにアクセスできない場合は、これが役立ちます。それ以外の場合は、オブジェクトの数によって異なります。私はあなたが最大10,000個のオブジェクトへのリフレクションを使うことができると思います(多分もう少し少なく)、これより多くのためにプロトコルバッファシリアライザはより良い性能を発揮するでしょう。

0
Roma Borodov

ディープコピーの場合は、ICloneableが正しい解決策ですが、ここではICloneableインターフェイスの代わりにコンストラクタを使用して、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();

コピーを作成する場所には、次のライブラリが必要です。

using System.Linq

system.Linqの代わりにforループを使うこともできますが、Linqはそれを簡潔できれいにします。同様に、他の答えが示唆しているように拡張方法などを作ることもできますが、それは必要ありません。

0
ztorstri

誰かがこれを読んだことがあってもラッキーです…でも、私のCloneメソッドでタイプオブジェクトのリストを返さないようにするために、私はインターフェースを作りました:

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

それから私は拡張子を指定しました:

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

そして、これが私のA/Vマーキングソフトウェアのインターフェースの実装です。私のClone()メソッドにVidMarkのリストを返させたい(ICloneableインターフェースには私のメソッドにオブジェクトのリストを返させたい)。

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

そして最後に、クラス内での拡張の使い方

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

//Other methods instantiate and fill the lists

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

誰かが好きですか?何か改善はありますか?

0
John Kurtz

JSONシリアライザとデシリアライザを使用してC#でオブジェクトを複製する簡単な方法があります。

拡張クラスを作成することができます:

using Newtonsoft.Json;

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

複製してオブジェクトにするには:

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

次のコードは、最小限の変更でリストに転送されます。

基本的には、連続するループごとに、より大きな範囲から新しい乱数を挿入することによって機能します。それ以上の数値がすでに存在する場合は、それらの乱数を1つ上にずらして、新しいより広い範囲の乱数インデックスに転送します。

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