C#でジェネリックリストを複製するにはどうすればよいですか?


593

C#のオブジェクトの一般的なリストがあり、そのリストを複製したいと考えています。リスト内のアイテムは複製可能ですが、オプションがないようですlist.Clone()

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


44
あなたは深いコピーまたは浅いコピーを探しているなら言うべき
のoriP

10
深くて浅いコピーとは何ですか?
大佐パニック


3
@orip clone()定義上、深いコピーではありませんか?C#では、=を使用してポインターを簡単に渡すことができると思いました。
クリス

13
@Chris浅いコピーは、ポインターコピーより1レベル深くコピーします。たとえば、リストの浅いコピーは同じ要素を持ちますが、異なるリストになります。
orip

回答:


385

拡張メソッドを使用できます。

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

71
List.ConvertAllは、常に配列全体をサイズ変更する必要があるのではなく、リスト全体の配列を事前に割り当てることができるため、これをより高速に実行できると思います。
MichaelGG 2008年

2
@MichaelGG、変換したくないが、リストのアイテムを複製/複製する場合はどうなりますか?これはうまくいくでしょうか?|| var clonedList = ListOfStrings.ConvertAll(p => p);
IbrarMumtaz 2014

29
@IbrarMumtaz:var clonedList = new List <string>(ListOfStrings);と同じです。
Brandon Arnold

4
素敵な解決策!ちなみに、私はpublic static List <T> CLone <T> ...を好みます。これ以上のキャストは必要ないので、このような場合により役立ちます。List <MyType> cloned = listToClone.Clone();
Plutoz、

2
これはディープクローニングです
ジョージビル

513

要素が値型の場合は、次のようにすることができます。

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.CopyFrom(YourType itemToCopy)新しいインスタンスを返すコピーコンストラクタまたはファクトリメソッドをお勧めしますYourType

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


1
List <T> .ConvertAllは、新しいリストを作成してforeach + addを実行するよりも見栄えが良いと思います。
MichaelGG 2008年

2
@Dimitri:いいえ、そうではありません。問題は、ICloneable定義されたときに、定義がクローンが深いか浅いかを決して述べていないため、オブジェクトがそれを実装するときにどのタイプのクローン操作が実行されるかを決定できないことです。つまり、のディープクローンを作成する場合は、それがディープコピーであることを確認List<T>せずICloneableに行う必要があります。
Jeff Yates

5
AddRangeメソッドを使用しないのはなぜですか?(newList.AddRange(oldList.Select(i => i.Clone())またはnewList.AddRange(oldList.Select(i => new YourType(i)
phoog

5
@phoog:コードをスキャンするとき、少し読みにくく、理解しにくいと思います、それだけです。私にとっては、読みやすさが優先されます。
ジェフイェーツ2010

1
@JeffYates:十分に考慮されていないしわの1つは、一般に、変更する実行パスが存在する場合にのみコピーする必要があるということです。それはです非常に不変型が可変型のインスタンスへの参照を保持するために持っている一般的な、しかしそれを変異させます何にでも、そのインスタンスを公開することはありません。変更されないものを不必要にコピーすると、パフォーマンスが大幅に低下し、メモリ使用量が桁違いに増える場合があります。
スーパーキャット2013

84

浅いコピーの場合は、代わりにジェネリックListクラスのGetRangeメソッドを使用できます。

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

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

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


43
これは、List <T>のコンストラクターを使用して、コピー元のList <T>を指定することでも実現できます。例:var浅いClonedList =新しいList <MyObject>(originalList);
Arkiliknam

9
よく使うList<int> newList = oldList.ToList()。同じ効果。ただし、Arkiliknamのソリューションは、私の意見では可読性に最適です。
Dan Bechard、2012年

82
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でこれを行う1つの方法です。オブジェクトはである必要があります[Serializable()]。目標は、すべての参照を失い、新しい参照を構築することです。


11
+1-私はこの答えが好きです-それは素早く、汚く、厄介で、非常に効果的です。Silverlightで使用しましたが、BinarySerializerが使用できないため、DataContractSerializerを使用しました。これを行うことができるのに、誰がオブジェクト複製コードのページを書く必要がありますか?:)
スラッグスター

3
私はこれが好き。「正しい」ことを行うのは良いことですが、迅速かつ汚いことがしばしば役に立ちます。
Odrade、

3
早く!しかし:なぜ汚いのですか?
レイザー

2
この深いクローンは高速で簡単です。このページの他の提案に注意してください。私はいくつか試してみましたが、それらはディープクローンをしていません。
RandallTo

2
これを呼び出すことができる場合、唯一の負の側面は、これを機能させるためにクラスをSerializableとマークする必要があることです。
Tuukka Haapaniemi 2015

30

リストのクローンを作成するには、.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 }
> 

3
はるかに
簡単な

29
少し警告これは浅いコピーです...これにより2つのリストオブジェクトが作成されますが、内部のオブジェクトは同じになります。つまり、1つのプロパティを変更すると、元のリストの同じオブジェクト/プロパティが変更されます。
マークG

22

少し変更した後、クローンを作成することもできます:

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

Tがシリアル化可能であることを忘れないでください。そうしないと、System.Runtime.Serialization.SerializationExceptionが発生します。
BenceVégert2017年

いい答えです。1つのヒント:if (!obj.GetType().IsSerializable) return default(T);例外を防ぐ最初のステートメントとして追加できます。そして、それを拡張メソッドに変更する場合、エルビス演算子のようにvar b = a?.DeepClone();var a = new List<string>() { "a", "b" }; たとえば与えられた)を使用することもできます。
マット

15

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

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

myList挿入や削除などの変更は影響せずcloneOfMyList、その逆も同様です。

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


私はuser49126に同意します。これは浅いコピーであり、一方のリストに加えられた変更が他方のリストに反映されることを確認しています。
Seidleroni、2015年

1
@Seidleroni、あなたは間違っている。itensリストに加えられた変更は他のリストに影響しますが、リスト自体の変更には影響しません。
ウェリントンザネリ

これは浅いコピーです。
Elliot Chen

これはどのように浅いコピーですか?
mko

2
@WellingtonZanelli myListから要素を削除すると、cloneOfMyListからも要素が削除されることを確認しました。
Nick Gallimore

13

AutoMapper(または任意のマッピングライブラリ)を使用してクローンを作成すると、シンプルで多くのメンテナンスが可能になります。

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

Mapper.CreateMap<YourType, YourType>();

魔法を使う:

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

13

値のタイプのみに関心がある場合...

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

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

15
これは要素を複製しません。
ジェフイェーツ

10

プロジェクトですでにNewtonsoft.Jsonを参照していて、オブジェクトがシリアル化可能である場合は、いつでも使用できます。

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

おそらく最も効率的な方法ではありませんが、数百回から数百回実行しない限り、速度の違いに気付くことはないでしょう。


4
速度の違いではなく、読みやすさの問題です。このコード行にたどり着くと、頭をたたいて、なぜサードパーティのライブラリを導入してオブジェクトをシリアル化してから逆シリアル化するのかと思いますが、なぜそれが起こっているのかわかりません。また、これは、循環構造を持つオブジェクトを含むモデルリストでは機能しません。
Jonathon Cwik、2015

1
このコードは、ディープクローニングに最適でした。アプリはドキュメントのボイラープレートをDevからQA、Prodに移行しています。各オブジェクトは複数のドキュメントテンプレートオブジェクトのパケットであり、各ドキュメントは段落オブジェクトのリストで構成されています。このコードにより、.NETの "ソース"オブジェクトをシリアル化し、すぐに新しい "ターゲット"オブジェクトに逆シリアル化して、別の環境のSQLデータベースに保存することができます。たくさんの研究の末、私はたくさんのものを見つけました、それらの多くはあまりにも面倒で、これを試すことにしました。この短くて柔軟なアプローチは「ちょうどいい」でした!
Developer63

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

私の友人であるGregor Martinovicと私は、JavaScriptシリアライザーを使用したこの簡単なソリューションを思いつきました。クラスをSerializableとしてフラグを立てる必要はありません。テストでは、Newtonsoft JsonSerializerを使用してBinaryFormatterを使用するよりも高速にフラグを立てます。すべてのオブジェクトで使用できる拡張メソッド。

標準の.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
プライベートメンバーは、JSONメソッドを使用して複製されません。stackoverflow.com/a/78612/885627
himanshupareek66

3
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();

3

誰かがこれを読んだら幸いですが、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();
}

誰もそれを好きですか?何か改善はありますか?


2

また、を使用してリストを配列に変換し、を使用して配列をToArray複製することもできArray.Clone(...)ます。ニーズによっては、Arrayクラスに含まれているメソッドがニーズを満たす場合があります。


これは動作しません; 複製された配列の値を変更しても、元のリストの値は変更されます。
ベルヌーイトカゲ

var clonedList = ListOfStrings.ConvertAll(p => p);を使用できます。@IbrarMumtazによって与えられたように....効果的に機能します... 1つのリストに対する変更はそれ自体に保持され、別のリストには反映されません
zainul

2

あなたは拡張メソッドを使うことができます:

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

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

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

1

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

一部のコレクション(たとえば、SilverlightでのDataGridのSelectedItems)は、このアプローチの問題であるCopyToの実装をスキップするようです
George Birbilis

1

オートマッパーを使用してオブジェクトをコピーします。1つのオブジェクトをそれ自体にマッピングするマッピングをセットアップするだけです。この操作は好きなようにラップできます。

http://automapper.codeplex.com/


1

キャストを使用すると、この場合、浅いコピーに役立ちます。

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

ディープコピーの場合、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はそれを簡潔でクリーンにします。同様に、他の回答が示唆しているように、拡張メソッドなどを作成することもできますが、その必要はありません。


これは「コピーコンストラクタ」と呼ばれます。エラーが発生しやすいアプローチです。Studentに新しいフィールドを追加するときは、必ずコピーコンストラクターに追加する必要があります。「クローン」の背後にある主なアイデアは、その問題を回避することです。
kenno

2
ICloneableを使用する場合でも、クラスに "Clone"メソッドが必要です。リフレクション(上記の方法でも使用できます)を使用しない限り、そのCloneメソッドは、上記のコピーコンストラクターの方法と同じように見え、新しいフィールドや変更されたフィールドを更新する必要があるという同じ問題に悩まされます。しかし、それは「クラスのフィールドが変更されたときにクラスを更新する必要がある」ということです。もちろんそうです;)
ztorstri

0

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

基本的には、連続するループごとに、より大きな範囲から新しい乱数を挿入することで機能します。同じかそれ以上の数値がすでに存在する場合は、それらの乱数を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

もう1つ:リフレクションを使用できます。これを適切にキャッシュすると、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万クラスの改善には疑問があります。高速なprotobuf / newtonを試してみます。

PS:わかりやすくするために、ここでは自動プロパティのみを使用しました。FieldInfoで更新することもできますが、自分で簡単に実装する必要があります。

最近、DeepClone関数を使用してプロトコルバッファシリアライザをテストしました。100万の単純なオブジェクトでは4.2秒で勝利しますが、内部オブジェクトでは7.4秒で勝利します。

Serializer.DeepClone(personList);

概要:クラスにアクセスできない場合は、これが役立ちます。それ以外の場合は、オブジェクトの数によって異なります。最大で10,000オブジェクト(多分少し少ない)のリフレクションを使用できると思いますが、それ以上の場合、プロトコルバッファーシリアライザーのパフォーマンスが向上します。


0

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;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.