Q. なぜこの回答を選択するのですか?
- .NET で可能な最速の速度が必要な場合は、この回答を選択してください。
- 本当に簡単なクローン作成方法が必要な場合は、この回答を無視してください。
つまり、修正が必要なパフォーマンスのボトルネックがなく、プロファイラーでそれを証明できない限り、別の回答を使用してください。
他の方法よりも 10 倍高速
ディープ クローンを実行する次の方法は次のとおりです。
- シリアライゼーション/デシリアライゼーションを伴うものよりも 10 倍高速です。
- .NET が可能な理論上の最大速度にかなり近い速度です。
そして方法は...
究極の速度を得るには、Nested MemberwiseClone を使用してディープ コピーを実行できます。値構造体のコピーとほぼ同じ速度であり、(a) リフレクションまたは (b) シリアル化 (このページの他の回答で説明されている) よりもはるかに高速です。
ネストされた MemberwiseClone をディープ コピーに使用する場合は、クラス内のネストされたレベルごとに ShallowCopy を手動で実装し、上記のすべての ShallowCopy メソッドを呼び出して完全なクローンを作成する DeepCopy を実装する必要があることに注意してください。これは簡単です。全部で数行だけです。以下のデモ コードを参照してください。
以下は、100,000 個のクローンの相対的なパフォーマンスの違いを示すコードの出力です。
- ネストされた構造体でネストされた MemberwiseClone の場合は 1.08 秒
- ネストされたクラスのネストされた MemberwiseClone の場合は 4.77 秒
- シリアライゼーション/デシリアライゼーションに 39.93 秒
ネストされた MemberwiseClone をクラスで使用すると、構造体をコピーするのとほぼ同じ速さで、構造体のコピーは、.NET が可能な理論上の最大速度に非常に近い速度になります。
Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:04.7795670,30000000
Demo 2 of shallow and deep copy, using structs and value copying:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details:
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:01.0875454,30000000
Demo 3 of deep copy, using class and serialize/deserialize:
Elapsed time: 00:00:39.9339425,30000000
MemberwiseCopy を使用してディープ コピーを行う方法を理解するために、上記の時間を生成するために使用されたデモ プロジェクトを次に示します。
// Nested MemberwiseClone example.
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
public Person(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
[Serializable] // Not required if using MemberwiseClone
public class PurchaseType
{
public string Description;
public PurchaseType ShallowCopy()
{
return (PurchaseType)this.MemberwiseClone();
}
}
public PurchaseType Purchase = new PurchaseType();
public int Age;
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person DeepCopy()
{
// Clone the root ...
Person other = (Person) this.MemberwiseClone();
// ... then clone the nested class.
other.Purchase = this.Purchase.ShallowCopy();
return other;
}
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
public PersonStruct(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
public struct PurchaseType
{
public string Description;
}
public PurchaseType Purchase;
public int Age;
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct ShallowCopy()
{
return (PersonStruct)this;
}
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct DeepCopy()
{
return (PersonStruct)this;
}
}
// Added only for a speed comparison.
public class MyDeepCopy
{
public static T DeepCopy<T>(T obj)
{
object result = null;
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
result = (T)formatter.Deserialize(ms);
ms.Close();
}
return (T)result;
}
}
次に、main からデモを呼び出します。
void MyMain(string[] args)
{
{
Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
var Bob = new Person(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
}
{
Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
var Bob = new PersonStruct(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details:\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
}
{
Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
int total = 0;
var sw = new Stopwatch();
sw.Start();
var Bob = new Person(30, "Lamborghini");
for (int i = 0; i < 100000; i++)
{
var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
total += BobsSon.Age;
}
Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
Console.ReadKey();
}
繰り返しになりますが、ネストされた MemberwiseClone をディープ コピーに使用する場合は、クラス内のネストされたレベルごとに ShallowCopy を手動で実装し、完全なクローンを作成するためにすべての上記の ShallowCopy メソッドを呼び出す DeepCopy を実装する必要があることに注意してください。これは簡単です。全部で数行だけです。上記のデモ コードを参照してください。
値型と参照型
オブジェクトの複製に関しては、「構造体」と「クラス」には大きな違いがあることに注意してください。
- 「struct」がある場合、それは値型であるため、コピーするだけで内容が複製されます (ただし、この投稿の手法を使用しない限り、浅い複製しか作成されません)。
- 「クラス」がある場合は参照型なので、コピーする場合はそこへのポインタをコピーするだけです。真のクローンを作成するには、より創造的である必要があり、メモリ内に元のオブジェクトの別のコピーを作成する値型と参照型の違いを使用する必要があります。
値型と参照型の違いを参照してください。
デバッグに役立つチェックサム
- オブジェクトのクローンを正しく作成しないと、特定が非常に困難なバグが発生する可能性があります。製品コードでは、チェックサムを実装して、オブジェクトが適切に複製され、別の参照によって破損していないことを再確認する傾向があります。このチェックサムは、リリース モードでオフにすることができます。
- この方法は非常に便利だと思います。多くの場合、オブジェクト全体ではなく、オブジェクトの一部だけをクローンしたい場合があります。
多くのスレッドを他の多くのスレッドから分離するのに非常に便利
このコードの優れた使用例の 1 つは、ネストされたクラスまたは構造体のクローンをキューにフィードして、生産者/消費者パターンを実装することです。
- 1 つ (または複数) のスレッドが所有するクラスを変更し、このクラスの完全なコピーを にプッシュすることができます
ConcurrentQueue
。
- 次に、これらのクラスのコピーを取り出して処理する 1 つ (または複数) のスレッドを用意します。
これは実際には非常にうまく機能し、多くのスレッド (プロデューサー) を 1 つ以上のスレッド (コンシューマー) から切り離すことができます。
また、この方法も驚くほど高速です。ネストされた構造体を使用すると、ネストされたクラスをシリアライズ/デシリアライズするよりも 35 倍速く、マシンで利用可能なすべてのスレッドを活用できます。
アップデート
どうやら、ExpressMapper は、上記のようなハンド コーディングよりも高速ではないにしても、同じくらい高速です。それらがプロファイラーとどのように比較されるかを確認する必要があるかもしれません。