私は何かをしたいのですが。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
その後、元のオブジェクトに反映されていない新しいオブジェクトに変更を加えます。
この機能はあまり必要ないので、必要に応じて新しいオブジェクトを作成して各プロパティを個別にコピーすることにしました。状況。
変更を元のオブジェクトに反映させずに複製されたオブジェクトを変更できるように、オブジェクトを複製またはディープコピーする方法を教えてください。
標準的なプラクティスは ICloneable
インターフェイス( ここ で説明されているため、逆流しません)を実装することですが、ここで見つけたNice deep cloneオブジェクトコピー機は コードプロジェクト 少し前に、それを私たちのものに組み込みました。
他の場所で述べたように、オブジェクトをシリアル化できる必要があります。
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of the object.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", nameof(source));
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
これは、オブジェクトをシリアル化してから、新しいオブジェクトにシリアル化解除するという考え方です。利点は、オブジェクトが複雑になりすぎたときにすべてのクローンを作成することを心配する必要がないことです。
また、拡張メソッドを使用すると(元々参照されていたソースからも):
C#3.0の新しい 拡張メソッド を使用する場合は、メソッドを次の署名を持つように変更します。
public static T Clone<T>(this T source)
{
//...
}
これで、メソッド呼び出しは単にobjectBeingCloned.Clone();
になります。
EDIT(2015年1月10日)これを再考すると思いますが、最近これを行うために(Newtonsoft)Jsonを使用し始めたので、 はずです より軽く、[Serializable]タグのオーバーヘッドを回避します。 (NB@atconwayは、JSONメソッドを使用してプライベートメンバーが複製されないことをコメントで指摘しています)
/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
私は主にプリミティブとリストの非常に単純なオブジェクトのクローンを欲していました。あなたのオブジェクトがそのままJSONシリアライズ可能であれば、このメソッドがうまくいきます。これには、クローンクラスのインターフェースを変更したり実装したりする必要はなく、JSON.NETのようなJSONシリアライザが必要です。
public static T Clone<T>(T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
また、この拡張方法を使うことができます
public static class SystemExtension
{
public static T Clone<T>(this T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
}
ICloneable を使用しない理由は、 not ですが、それには汎用インターフェースがないからです。 使わないのは曖昧だから 。あなたが浅いコピーを取っているのか、それとも深いコピーを取っているのかは明らかではありません。それは実装者次第です。
はい、MemberwiseClone
は浅いコピーを作成しますが、MemberwiseClone
の反対はClone
ではありません。おそらくそれは存在しないDeepClone
でしょう。 ICloneableインターフェイスを介してオブジェクトを使用すると、基になるオブジェクトがどの種類の複製を実行しているのかわかりません。 (そして、XMLのコメントでは明確にはなりません。オブジェクトのCloneメソッドのコメントではなく、インターフェースのコメントが得られるからです。)
私が普段やっていることは、Copy
メソッドを単に作ることです。
ここにリンクされている多くのオプションとこの問題の可能な解決策について多くを読んだ後、 すべてのオプションはIan P 's link (all他のオプションはそれらのバリエーションです)、最良の解決策は Pedro77 's link の質問コメントで提供されます。
そこで、ここでこれらの2つの参照の関連部分をコピーします。そうすれば、次のことができます。
何よりもまず、これらがすべてのオプションです。
式ツリーによる記事の高速ディープコピー には、シリアル化、リフレクション、および式ツリーによるクローニングのパフォーマンス比較もあります。
Mr Venkat Subramaniam(ここの冗長リンク)は、理由を詳細に説明しています 。
彼のすべての記事は、3つのオブジェクトを使用して、ほとんどの場合に適用できるようにしようとする例を取り巻いています:Person、BrainおよびCity。私たちは、同じ都市でありながら独自の頭脳を持つ人物をクローンしたいと考えています。上記の他の方法のいずれかが記事をもたらすまたは読むことができるすべての問題を想像することができます。
これは彼の結論を少し修正したバージョンです。
New
の後にクラス名を指定してオブジェクトをコピーすると、多くの場合、拡張不可能なコードになります。これを実現するには、プロトタイプパターンのアプリケーションであるクローンを使用するのがより良い方法です。ただし、C#(およびJava)で提供されているとおりにcloneを使用することも、非常に問題になる可能性があります。保護された(非パブリック)コピーコンストラクターを提供し、cloneメソッドから呼び出すことをお勧めします。これにより、オブジェクトを作成するタスクをクラス自体のインスタンスに委任できるため、拡張性が提供され、保護されたコピーコンストラクターを使用してオブジェクトを安全に作成できます。
うまくいけば、この実装が物事を明確にすることができます:
public class Person : ICloneable
{
private final Brain brain; // brain is final since I do not want
// any transplant on it once created!
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
protected Person(Person another)
{
Brain refBrain = null;
try
{
refBrain = (Brain) another.brain.clone();
// You can set the brain in the constructor
}
catch(CloneNotSupportedException e) {}
brain = refBrain;
age = another.age;
}
public String toString()
{
return "This is person with " + brain;
// Not meant to sound rude as it reads!
}
public Object clone()
{
return new Person(this);
}
…
}
ここで、Personから派生したクラスを持つことを検討してください。
public class SkilledPerson extends Person
{
private String theSkills;
public SkilledPerson(Brain aBrain, int theAge, String skills)
{
super(aBrain, theAge);
theSkills = skills;
}
protected SkilledPerson(SkilledPerson another)
{
super(another);
theSkills = another.theSkills;
}
public Object clone()
{
return new SkilledPerson(this);
}
public String toString()
{
return "SkilledPerson: " + super.toString();
}
}
次のコードを実行してみてください。
public class User
{
public static void play(Person p)
{
Person another = (Person) p.clone();
System.out.println(p);
System.out.println(another);
}
public static void main(String[] args)
{
Person sam = new Person(new Brain(), 1);
play(sam);
SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
play(bob);
}
}
生成される出力は次のとおりです。
This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e
オブジェクトの数のカウントを保持する場合、ここで実装されるクローンはオブジェクトの数の正しいカウントを保持することに注意してください。
私はクローンよりコピーコンストラクタを好む。意図が明確になりました。
すべてのパブリックプロパティをコピーする簡単な拡張方法任意のオブジェクトに対して機能し、 は必要ありません - クラスを[Serializable]
にする必要はありません。他のアクセスレベルに拡張することができます。
public static void CopyTo( this object S, object T )
{
foreach( var pS in S.GetType().GetProperties() )
{
foreach( var pT in T.GetType().GetProperties() )
{
if( pT.Name != pS.Name ) continue;
( pT.GetSetMethod() ).Invoke( T, new object[]
{ pS.GetGetMethod().Invoke( S, null ) } );
}
};
}
私はSilverlightでICloneableを使用するのに問題がありましたが、Seralizationのアイデアが気に入ったので、XMLをSeralizeできます。
static public class SerializeHelper
{
//Michael White, Holly Springs Consulting, 2009
//[email protected]
public static T DeserializeXML<T>(string xmlData) where T:new()
{
if (string.IsNullOrEmpty(xmlData))
return default(T);
TextReader tr = new StringReader(xmlData);
T DocItms = new T();
XmlSerializer xms = new XmlSerializer(DocItms.GetType());
DocItms = (T)xms.Deserialize(tr);
return DocItms == null ? default(T) : DocItms;
}
public static string SeralizeObjectToXML<T>(T xmlObject)
{
StringBuilder sbTR = new StringBuilder();
XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
XmlWriterSettings xwsTR = new XmlWriterSettings();
XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
xmsTR.Serialize(xmwTR,xmlObject);
return sbTR.ToString();
}
public static T CloneObject<T>(T objClone) where T:new()
{
string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
return SerializeHelper.DeserializeXML<T>(GetString);
}
}
CloneExtensions
libraryprojectを作成しました。 Expression Treeランタイムコードのコンパイルによって生成された単純な代入操作を使用して、高速でディープクローンを実行します。
使い方は?
独自のClone
name__メソッドまたはCopy
name__メソッドを、フィールドとプロパティの間の割り当てトーンを使って記述するのではなく、式ツリーを使用してプログラムが自分で行うようにします。拡張メソッドとしてマークされたGetClone<T>()
メソッドを使用すると、インスタンス上で単純に呼び出すことができます。
var newInstance = source.GetClone();
source
enumを使用して、newInstance
name__からCloningFlags
name__に何をコピーするかを選択できます。
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
クローンできるもの
以下のクラス/構造体メンバは内部的に複製されています。
どれくらい速いですか?
与えられた型T
name__に対してGetClone<T>
が初めて使用されるまでは、メンバー情報を一度だけ収集すればよいため、解決策はリフレクションよりも高速です。
クローンを作成して同じ型のインスタンスを結合する場合は、シリアル化ベースのソリューションよりも高速です(T
name__)。
そしてもっと...
documentation で生成された式についてもっと読んでください。
List<int>
のサンプル式デバッグリスト:
.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
System.Collections.Generic.List`1[System.Int32] $source,
CloneExtensions.CloningFlags $flags,
System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
.Block(System.Collections.Generic.List`1[System.Int32] $target) {
.If ($source == null) {
.Return #Label1 { null }
} .Else {
.Default(System.Void)
};
.If (
.Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
) {
$target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
).Invoke((System.Object)$source)
} .Else {
$target = .New System.Collections.Generic.List`1[System.Int32]()
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
) {
.Default(System.Void)
} .Else {
.Default(System.Void)
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
) {
.Block() {
$target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
$source.Capacity,
$flags,
$initializers)
}
} .Else {
.Default(System.Void)
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
) {
.Block(
System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
System.Collections.Generic.ICollection`1[System.Int32] $var2) {
$var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
$var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
.Loop {
.If (.Call $var1.MoveNext() != False) {
.Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
$var1.Current,
$flags,
$initializers))
} .Else {
.Break #Label2 { }
}
}
.LabelTarget #Label2:
}
} .Else {
.Default(System.Void)
};
.Label
$target
.LabelTarget #Label1:
}
}
次のC#コードのように同じ意味を持ちます。
(source, flags, initializers) =>
{
if(source == null)
return null;
if(initializers.ContainsKey(typeof(List<int>))
target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
else
target = new List<int>();
if((flags & CloningFlags.Properties) == CloningFlags.Properties)
{
target.Capacity = target.Capacity.GetClone(flags, initializers);
}
if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
{
var targetCollection = (ICollection<int>)target;
foreach(var item in (ICollection<int>)source)
{
targetCollection.Add(item.Clone(flags, initializers));
}
}
return target;
}
List<int>
のためにあなたがあなた自身のClone
name__メソッドを書く方法と全く同じではないですか?
ValueInjecter または Automapper のようなサードパーティ製アプリケーションを既に使用している場合は、次のようにすることができます。
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
このメソッドを使用すると、オブジェクトにISerializableまたはICloneableを実装する必要がなくなります。これはMVC/MVVMパターンと共通なので、このような単純なツールが作成されました。
簡単に言えば、ICloneableインターフェイスから継承して、次に.clone関数を実装するということです。クローンは、メンバー単位のコピーを実行し、それを必要とするすべてのメンバーに対してディープコピーを実行してから、結果のオブジェクトを返す必要があります。これは再帰的な操作です(クローンを作成するクラスのすべてのメンバが値型かICloneableを実装していること、およびそれらのメンバが値型またはICloneableを実装していることなどが必要です)。
ICloneableを使用したクローン作成の詳細については、 この記事 を参照してください。
long という答えは「それが依存する」ということです。他の人が述べたように、ICloneableは総称ではサポートされておらず、循環クラス参照については特別な考慮が必要であり、.NET Frameworkでは "間違い" として実際に見られています。直列化の方法は、オブジェクトが直列化可能かどうかによって異なります。オブジェクトは直列化できないため、制御できません。コミュニティではまだ「ベスト」プラクティスである多くの議論があります。実際には、ICloneableがもともとあると解釈されていたような、すべての状況に対応するすべてのベストプラクティスに適したものではありません。
その他のオプションについては、this Developer's Cornerの記事 を参照してください(Ianの功績による)。
最善の方法は拡張メソッドを実装することです。
public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
そしてそれを次のようにして解の中のどこにでも使ってください。
var copy = anyObject.DeepClone();
次の3つの実装があります。
リンクされた方法はすべてうまく機能しており、徹底的にテストされています。
乾杯。
未知の型への本当のクローン作成が欲しいなら、 fastclone を見てください。
これは、バイナリシリアライゼーションよりも約10倍高速に動作し、完全なオブジェクトグラフの整合性を維持する、表現ベースのクローン作成です。
つまり、階層内の同じオブジェクトを複数回参照した場合、クローンでも単一インスタンスが参照されます。
複製されるオブジェクトにインタフェース、属性、その他の変更を加える必要はありません。
物事をシンプルに保ち、 AutoMapper を使ってください。他の人が述べたように、あるオブジェクトを別のオブジェクトにマッピングするのは簡単な小さなライブラリです。
MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);
ターゲットオブジェクトはソースオブジェクトのコピーになりました。単純すぎない?ソリューション内のあらゆる場所で使用する拡張メソッドを作成します。
public static T Copy<T>(this T source)
{
T copy = default(T);
Mapper.CreateMap<T, T>();
copy = Mapper.Map<T, T>(source);
return copy;
}
拡張メソッドを使用すると、3行が1行になります。
MyType copy = source.Copy();
List <T>を手動でディープコピーするという .NET 欠点を克服するためにこれを思いつきました。
私はこれを使う:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
foreach (SpotPlacement sp in spotPlacements)
{
yield return (SpotPlacement)sp.Clone();
}
}
そして別の場所で:
public object Clone()
{
OrderItem newOrderItem = new OrderItem();
...
newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
...
return newOrderItem;
}
これを行うonelinerを考え出そうとしましたが、無名メソッドブロック内でyieldが機能しないため、不可能です。
もっと良いのは、一般的なList <T>クローンを使うことです。
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}
言い換えれば、 修正が必要なパフォーマンス上のボトルネックがある場合を除き、別の答えを使ってください。プロファイラでそれを証明できます 。
ディープクローンを実行する方法は次のとおりです。
最高のスピードを得るためには、入れ子になったMemberwiseCloneを使ってディープコピーをすることができます。構造体をコピーするのとほぼ同じスピードで、(a)リフレクションや(b)シリアライゼーションよりもはるかに速いですこのページで).
ifディープコピーにNested MemberwiseCloneを使用する場合は、クラス内のネストされた各レベルにShallowCopyを手動で実装する必要があります。)これは簡単です。合計数行だけで、下のデモコードを見てください。
これは100,000クローンの相対的なパフォーマンスの違いを示すコードの出力です。
入れ子になった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();
}
繰り返しますが、ifディープコピーにNested MemberwiseCloneを使用する場合は、クラス内のネストされた各レベルにShallowCopyを手動で実装する必要があります。 clone。これは簡単です:合計でほんの数行で、上のデモコードを見てください。
オブジェクトのクローン作成に関しては、 "struct"と "class")の間には大きな違いがあります。
値型と参照型の違い を参照してください。
このコードの優れたユースケースの1つは、プロデューサ/コンシューマパターンを実装するために、ネストしたクラスまたは構造体のクローンをキューに入れることです。
ConcurrentQueue
にプッシュすることができます。これは実際には非常にうまく機能し、1つ以上のスレッド(コンシューマ)から多くのスレッド(プロデューサ)を切り離すことを可能にします。
ネストされた構造体を使用する場合は、ネストされたクラスをシリアライズ/デシリアライズするよりも35倍速く、マシン上で利用可能なすべてのスレッドを利用することができます。
明らかに、ExpressMapperは上記のようなハンドコーディングよりも速くはないにしても同じくらい速いです。私は彼らがプロファイラーと比較する方法を見なければならないかもしれません。
私はそれが反射を通しても実装されているのを見ました。基本的に、オブジェクトのメンバを繰り返し処理してそれらを新しいオブジェクトに適切にコピーするメソッドがありました。それが参照型またはコレクションに到達したとき、私はそれがそれ自身で再帰呼び出しをしたと思います。反射は高価ですが、うまく機能しました。
さまざまなプロジェクトで自分のすべての要件を満たすクローンを見つけることができなかったので、自分のコードをクローン要件に合うように調整するのではなく、さまざまなコード構造に合わせて構成および適応できる深いクローンを作成しました。これは、複製されるコードにアノテーションを追加することによって達成されるか、またはデフォルトの動作を維持するためにコードをそのままにします。リフレクション、タイプキャッシュを使い、 fastflect に基づいています。クローン作成プロセスは、膨大な量のデータと高いオブジェクト階層(他のリフレクション/シリアライゼーションベースのアルゴリズムと比較して)に対して非常に高速です。
https://github.com/kalisohn/CloneBehave
Nugetパッケージとしても利用可能です: https://www.nuget.org/packages/Clone.Behave/1.0.0
たとえば、次のコードはAddressをdeepCloneしますが、_currentJobフィールドの簡易コピーのみを実行します。
public class Person
{
[DeepClone(DeepCloneBehavior.Shallow)]
private Job _currentJob;
public string Name { get; set; }
public Job CurrentJob
{
get{ return _currentJob; }
set{ _currentJob = value; }
}
public Person Manager { get; set; }
}
public class Address
{
public Person PersonLivingHere { get; set; }
}
Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");
Address adrClone = adr.Clone();
//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
一般に、あなたはICloneableインターフェースを実装し、Cloneを自分で実装します。 C#オブジェクトには、浅いコピーを実行するMemberwiseCloneメソッドが組み込まれています。
ディープコピーの場合は、自動的に実行する方法を知ることはできません。
これがディープコピーの実装です。
public static object CloneObject(object opSource)
{
//grab the type and create a new instance of that type
Type opSourceType = opSource.GetType();
object opTarget = CreateInstanceOfType(opSourceType);
//grab the properties
PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
//iterate over the properties and if it has a 'set' method assign it from the source TO the target
foreach (PropertyInfo item in opPropertyInfo)
{
if (item.CanWrite)
{
//value types can simply be 'set'
if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
{
item.SetValue(opTarget, item.GetValue(opSource, null), null);
}
//object/complex types need to recursively call this method until the end of the tree is reached
else
{
object opPropertyValue = item.GetValue(opSource, null);
if (opPropertyValue == null)
{
item.SetValue(opTarget, null, null);
}
else
{
item.SetValue(opTarget, CloneObject(opPropertyValue), null);
}
}
}
}
//return the new item
return opTarget;
}
この方法で問題は解決しました。
private static MyObj DeepCopy(MyObj source)
{
var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);
}
これを次のように使用してください。MyObj a = DeepCopy(b);
私はそのようなCopyconstructorsが好きです:
public AnyObject(AnyObject anyObject)
{
foreach (var property in typeof(AnyObject).GetProperties())
{
property.SetValue(this, property.GetValue(anyObject));
}
foreach (var field in typeof(AnyObject).GetFields())
{
field.SetValue(this, field.GetValue(anyObject));
}
}
もっとコピーするものがあればそれらを追加してください
手作業による実装からリフレクションまでのシリアライゼーションからたくさんのアイデアを見てきました、そして CGbR Code Generator を使って全く異なるアプローチを提案したいです。クローン生成方法は、メモリとCPUの効率が高いため、標準のDataContractSerializerの300倍高速です。
必要なのはICloneable
による部分クラス定義だけで、残りはジェネレータが行います。
public partial class Root : ICloneable
{
public Root(int number)
{
_number = number;
}
private int _number;
public Partial[] Partials { get; set; }
public IList<ulong> Numbers { get; set; }
public object Clone()
{
return Clone(true);
}
private Root()
{
}
}
public partial class Root
{
public Root Clone(bool deep)
{
var copy = new Root();
// All value types can be simply copied
copy._number = _number;
if (deep)
{
// In a deep clone the references are cloned
var tempPartials = new Partial[Partials.Length];
for (var i = 0; i < Partials.Length; i++)
{
var value = Partials[i];
value = value.Clone(true);
tempPartials[i] = value;
}
copy.Partials = tempPartials;
var tempNumbers = new List<ulong>(Numbers.Count);
for (var i = 0; i < Numbers.Count; i++)
{
var value = Numbers[i];
tempNumbers.Add(value);
}
copy.Numbers = tempNumbers;
}
else
{
// In a shallow clone only references are copied
copy.Partials = Partials;
copy.Numbers = Numbers;
}
return copy;
}
}
注: 最新バージョンでは、より多くのnullチェックがありますが、理解を深めるために省略しました。
ここでは速くて簡単な解決策が私のためにシリアル化/デシリアライゼーションに頼らずにうまくいきました。
public class MyClass
{
public virtual MyClass DeepClone()
{
var returnObj = (MyClass)MemberwiseClone();
var type = returnObj.GetType();
var fieldInfoArray = type.GetRuntimeFields().ToArray();
foreach (var fieldInfo in fieldInfoArray)
{
object sourceFieldValue = fieldInfo.GetValue(this);
if (!(sourceFieldValue is MyClass))
{
continue;
}
var sourceObj = (MyClass)sourceFieldValue;
var clonedObj = sourceObj.DeepClone();
fieldInfo.SetValue(returnObj, clonedObj);
}
return returnObj;
}
}
_編集_ :必須
using System.Linq;
using System.Reflection;
それが私がそれを使った方法です
public MyClass Clone(MyClass theObjectIneededToClone)
{
MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
私はあなたがこれを試すことができると思います。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
次の手順を実行します:
Self
を返す読み取り専用のT
プロパティ、およびISelf<T>
から派生したメソッドT Clone()
を含むICloneable<out T>
を使用してISelf<T>
を定義します。CloneBase
をキャストするprotected virtual generic VirtualClone
を実装するMemberwiseClone
型を定義します。VirtualClone
を実装する必要があります。最大限の継承の汎用性のために、パブリッククローン作成機能を公開するクラスはsealed
であるべきですが、それ以外はクローン作成の欠如を除いて同一である基本クラスから派生します。明示的な複製可能型の変数を渡すのではなく、ICloneable<theNonCloneableType>
型のパラメーターを使用してください。これにより、Foo
のクローン可能な派生物を期待するルーチンがDerivedFoo
のクローン可能な派生物と共に機能することが可能になりますが、Foo
のクローン不可能な派生物の作成も可能になります。
私は、 '[Serializable]'と '[DataContract]'の両方で機能する、受け入れられた答えのバージョンを作成しました。私がそれを書いてからしばらく時間が経ちました、しかし私が正しく覚えているならば[DataContract]は別のシリアライザを必要としました。
システム、System.IO、System.Runtime.Serialization、System.Runtime.Serialization.Formatters.Binary、System.Xml ;が必要です。
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (typeof(T).IsSerializable == true)
{
return CloneUsingSerializable<T>(source);
}
if (IsDataContract(typeof(T)) == true)
{
return CloneUsingDataContracts<T>(source);
}
throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
}
/// <summary>
/// Perform a deep Copy of an object that is marked with '[Serializable]'
/// </summary>
/// <remarks>
/// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
/// Uses code found on CodeProject, which allows free use in third party apps
/// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// </remarks>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneUsingSerializable<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
/// <summary>
/// Perform a deep Copy of an object that is marked with '[DataContract]'
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneUsingDataContracts<T>(T source)
{
if (IsDataContract(typeof(T)) == false)
{
throw new ArgumentException("The type must be a data contract.", "source");
}
// ** Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
DataContractSerializer dcs = new DataContractSerializer(typeof(T));
using(Stream stream = new MemoryStream())
{
using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
{
dcs.WriteObject(writer, source);
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
{
return (T)dcs.ReadObject(reader);
}
}
}
}
/// <summary>
/// Helper function to check if a class is a [DataContract]
/// </summary>
/// <param name="type">The type of the object to check.</param>
/// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
public static bool IsDataContract(Type type)
{
object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
return attributes.Length == 1;
}
}
オブジェクトツリーが直列化可能な場合は、このようなものも使用できます。
static public MyClass Clone(MyClass myClass)
{
MyClass clone;
XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
using (var ms = new MemoryStream())
{
ser.Serialize(ms, myClass);
ms.Position = 0;
clone = (MyClass)ser.Deserialize(ms);
}
return clone;
}
この解決策は非常に簡単ですが、他の解決策ほど高性能ではないことに注意してください。
また、Classが大きくなっても、クローン化されたフィールドのみが残っていることを確認してください。これも直列化されます。
クラスオブジェクトを複製するには、Object.MemberwiseCloneメソッドを使います。
この関数をあなたのクラスに追加するだけです:
public class yourClass
{
// ...
// ...
public yourClass DeepCopy()
{
yourClass othercopy = (yourClass)this.MemberwiseClone();
return othercopy;
}
}
その後、ディープ独立コピーを実行するには、DeepCopyメソッドを呼び出します。
yourClass newLine = oldLine.DeepCopy();
お役に立てれば。
さて、この記事にはリフレクションを使った明白な例がいくつかありますが、それを適切にキャッシュし始めるまでは通常リフレクションは遅くなります。
正しくキャッシュした場合、1000000のオブジェクトが4,6秒深くクローンされます(Watcherによる測定)。
static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
あなたがキャッシュされたプロパティを取るか、辞書に新しいを追加して単純にそれらを使用するよりも
foreach (var prop in propList)
{
var value = prop.GetValue(source, null);
prop.SetValue(copyInstance, value, null);
}
別の答えで私の記事に完全なコードチェック
この質問に対する答えのほとんどすべてが不十分であるか、私の状況では明らかに機能しないので、 AnyClone を作成しました。これは完全にリフレクションを使用して実装され、ここですべてのニーズを解決しました。複雑な構造を持つ複雑なシナリオでシリアライゼーションを機能させることはできませんでした。そしてIClonable
は理想的とは言えません - 実際には必要ないはずです。
標準の無視属性は、[IgnoreDataMember]
、[NonSerialized]
を使用してサポートされます。複雑なコレクション、セッターなしのプロパティ、読み取り専用フィールドなどをサポートします。
私がそれが私がしたのと同じ問題に出くわした他の誰かに役立つことを願っています。
Marc Gravells protobuf-netをあなたのシリアライザとして使うとき、受け入れられる答えは若干の修正を必要とします、それはコピーするオブジェクトが[Serializable]
で帰せられないので、そして直列化できず、Cloneメソッドは例外を投げるでしょう。
protobuf-netで動作するように修正しました。
public static T Clone<T>(this T source)
{
if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
== null)
{
throw new ArgumentException("Type has no ProtoContract!", "source");
}
if(Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = ProtoBuf.Serializer.CreateFormatter<T>();
using (Stream stream = new MemoryStream())
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
これは[ProtoContract]
属性の存在をチェックし、protobufs自身のフォーマッタを使ってオブジェクトをシリアル化します。
"not ISerializable "タイプもサポートするC#拡張。
public static class AppExtensions
{
public static T DeepClone<T>(this T a)
{
using (var stream = new MemoryStream())
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
serializer.Serialize(stream, a);
stream.Position = 0;
return (T)serializer.Deserialize(stream);
}
}
}
使用法
var obj2 = obj1.DeepClone()
特にクラス階層が重い場合は、IClonableインターフェイスにどれだけの労力を費やすことができるかは信じられません。また、MemberwiseCloneはどういうわけか奇妙に動作します - それは構造の通常のリスト型の種類でさえ正確に複製しません。
そしてもちろん、シリアル化のための最も興味深いジレンマは逆参照をシリアル化することです。親子関係があるクラス階層この場合、バイナリシリアライザが役に立つことを疑います。 (それは再帰的なループとスタックオーバーフローで終わります)。
私はどういうわけかここで提案された解決策が好き: どのようにあなたは.NETでオブジェクトのディープコピーをするのですか(特にC#)?
しかし - それはリストをサポートしていなかった、そのサポートを追加し、また再子育てを考慮に入れた。そのフィールドまたはプロパティに「parent」という名前を付けるようにしたルールのみをペアレント化する場合、DeepCloneでは無視されます。あなたは後方参照のためのあなた自身の規則を決めたいと思うかもしれません - ツリー階層のためにそれは "左/右"であるかもしれません、等...
これがテストコードを含む全体のコードスニペットです:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
namespace TestDeepClone
{
class Program
{
static void Main(string[] args)
{
A a = new A();
a.name = "main_A";
a.b_list.Add(new B(a) { name = "b1" });
a.b_list.Add(new B(a) { name = "b2" });
A a2 = (A)a.DeepClone();
a2.name = "second_A";
// Perform re-parenting manually after deep copy.
foreach( var b in a2.b_list )
b.parent = a2;
Debug.WriteLine("ok");
}
}
public class A
{
public String name = "one";
public List<String> list = new List<string>();
public List<String> null_list;
public List<B> b_list = new List<B>();
private int private_pleaseCopyMeAsWell = 5;
public override string ToString()
{
return "A(" + name + ")";
}
}
public class B
{
public B() { }
public B(A _parent) { parent = _parent; }
public A parent;
public String name = "two";
}
public static class ReflectionEx
{
public static Type GetUnderlyingType(this MemberInfo member)
{
Type type;
switch (member.MemberType)
{
case MemberTypes.Field:
type = ((FieldInfo)member).FieldType;
break;
case MemberTypes.Property:
type = ((PropertyInfo)member).PropertyType;
break;
case MemberTypes.Event:
type = ((EventInfo)member).EventHandlerType;
break;
default:
throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
}
return Nullable.GetUnderlyingType(type) ?? type;
}
/// <summary>
/// Gets fields and properties into one array.
/// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
/// </summary>
/// <param name="type">Type from which to get</param>
/// <returns>array of fields and properties</returns>
public static MemberInfo[] GetFieldsAndProperties(this Type type)
{
List<MemberInfo> fps = new List<MemberInfo>();
fps.AddRange(type.GetFields());
fps.AddRange(type.GetProperties());
fps = fps.OrderBy(x => x.MetadataToken).ToList();
return fps.ToArray();
}
public static object GetValue(this MemberInfo member, object target)
{
if (member is PropertyInfo)
{
return (member as PropertyInfo).GetValue(target, null);
}
else if (member is FieldInfo)
{
return (member as FieldInfo).GetValue(target);
}
else
{
throw new Exception("member must be either PropertyInfo or FieldInfo");
}
}
public static void SetValue(this MemberInfo member, object target, object value)
{
if (member is PropertyInfo)
{
(member as PropertyInfo).SetValue(target, value, null);
}
else if (member is FieldInfo)
{
(member as FieldInfo).SetValue(target, value);
}
else
{
throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
}
}
/// <summary>
/// Deep clones specific object.
/// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
/// This is now improved version (list support added)
/// </summary>
/// <param name="obj">object to be cloned</param>
/// <returns>full copy of object.</returns>
public static object DeepClone(this object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
if (obj is IList)
{
IList list = ((IList)obj);
IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);
foreach (object elem in list)
newlist.Add(DeepClone(elem));
return newlist;
} //if
if (type.IsValueType || type == typeof(string))
{
return obj;
}
else if (type.IsArray)
{
Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
var array = obj as Array;
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
copied.SetValue(DeepClone(array.GetValue(i)), i);
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = Activator.CreateInstance(obj.GetType());
MemberInfo[] fields = type.GetFieldsAndProperties();
foreach (MemberInfo field in fields)
{
// Don't clone parent back-reference classes. (Using special kind of naming 'parent'
// to indicate child's parent class.
if (field.Name == "parent")
{
continue;
}
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, DeepClone(fieldValue));
}
return toret;
}
else
{
// Don't know that type, don't know how to clone it.
if (Debugger.IsAttached)
Debugger.Break();
return null;
}
} //DeepClone
}
}
まだ別のJSON.NETの答え。このバージョンはISerializableを実装していないクラスで動作します。
public static class Cloner
{
public static T Clone<T>(T source)
{
if (ReferenceEquals(source, null))
return default(T);
var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
}
class ContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
}
マッパーはディープコピーを実行します。あなたのオブジェクトの各メンバは、新しいオブジェクトを作成し、そのすべての値を割り当てます。それは各非プリミティブ内部メンバーに再帰的に働きます。
私はあなたに最速の、現在活発に開発されているもののうちの1つを提案します。 UltraMapperをお勧めします https://github.com/maurosampietro/UltraMapper
Nugetパッケージ: https://www.nuget.org/packages/UltraMapper/ /
ディープクローニングとは、状態をコピーすることです。 .net
の場合stateはfieldsを意味します。
階層があるとしましょう:
static class RandomHelper
{
private static readonly Random random = new Random();
public static int Next(int maxValue) => random.Next(maxValue);
}
class A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}
class B : A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}
class C : B
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}
クローンを作成できます:
static class DeepCloneExtension
{
// consider instance fields, both public and non-public
private static readonly BindingFlags bindingFlags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
public static T DeepClone<T>(this T obj) where T : new()
{
var type = obj.GetType();
var result = (T)Activator.CreateInstance(type);
do
// copy all fields
foreach (var field in type.GetFields(bindingFlags))
field.SetValue(result, field.GetValue(obj));
// for every level of hierarchy
while ((type = type.BaseType) != typeof(object));
return result;
}
}
Demo1:
Console.WriteLine(new C());
Console.WriteLine(new C());
var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");
Console.WriteLine(new C());
Console.WriteLine(new C());
Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");
Console.WriteLine(new C());
Console.WriteLine(new C());
結果:
C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17
Image: C.random = 96 B.random = 18 A.random = 46
C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18
Clone: C.random = 96 B.random = 18 A.random = 46
C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79
すべての新しいオブジェクトにはrandom
フィールドのランダム値がありますが、clone
はimage
と完全に一致します。
Demo2:
class D
{
public event EventHandler Event;
public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}
// ...
var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");
image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");
image.RaiseEvent();
image.RaiseEvent();
var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");
clone.RaiseEvent();
image.RaiseEvent();
結果:
Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728
イベントバッキングフィールドもコピーされ、クライアントもクローンのイベントにサブスクライブされることに注意してください。
一般的なアプローチはすべて技術的に有効ですが、実際のディープコピーは実際にはほとんど必要ないため、自分でメモを追加したいと思いました。実際のビジネスアプリケーションで一般的なディープコピーを使用することは強くお勧めします。オブジェクトがコピーされてから明示的に変更されている場所では、見逃しがちです。
実際の状況では、データアクセスフレームワークとの連携だけでなく、実際にはコピーされたビジネスオブジェクトが100%同じになることはめったにないため、コピープロセスをできるだけ細かく制御したい場合もあります。オブジェクト参照を識別するためにORMによって使用されるreferenceIdの例を考えると、フルディープコピーでもこのidがコピーされるため、データストアに送信するとすぐにオブジェクトは異なります。とにかくコピーした後でこのプロパティを手動で変更しなければならず、オブジェクトが変更された場合は、一般的なディープコピーを使用するすべての場所でそれを調整する必要があります。
ICloneableを使って@cregox回答を拡張すると、実際には何がディープコピーになりますか?それは、元のオブジェクトと同じですが、別のメモリスペースを占有する、ヒープ上に新しく割り当てられたオブジェクトです。一般的なクローン機能を使用するのではなく、単に新しいオブジェクトを作成しないのですか。
私は自分のドメインオブジェクトに静的ファクトリメソッドの考え方を個人的に使用しています。
例:
public class Client
{
public string Name { get; set; }
protected Client()
{
}
public static Client Clone(Client copiedClient)
{
return new Client
{
Name = copiedClient.Name
};
}
}
public class Shop
{
public string Name { get; set; }
public string Address { get; set; }
public ICollection<Client> Clients { get; set; }
public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
{
var copiedClients = new List<Client>();
foreach (var client in copiedShop.Clients)
{
copiedClients.Add(Client.Clone(client));
}
return new Shop
{
Name = copiedShop.Name,
Address = newAddress,
Clients = copiedClients
};
}
}
コピープロセスを完全に制御しながら、オブジェクトのインスタンス化を構成する方法を誰かが探しているのであれば、それは私が個人的に非常に成功している解決策です。保護されたコンストラクタもそれを可能にします、他の開発者はオブジェクトの内部に構築ロジックをカプセル化するオブジェクトインスタンス化のきちんとした単一点を与えるファクトリメソッドを使用することを強いられます。メソッドをオーバーロードして、必要に応じて異なる場所にいくつかのクローンロジックを作成することもできます。
免責事項:私は言及されたパッケージの著者です。
2019年のこの質問に対するトップの回答が依然としてシリアル化またはリフレクションを使用していることに驚きました。
BinaryFormatter
にはSerializable
属性が必要です、JsonConverter
にはパラメーターなしのコンストラクターまたは属性が必要です。読み取り専用フィールドまたはインターフェースをうまく処理できず、両方とも必要以上に10-30倍遅いです。
代わりに、Expression TreesまたはReflection.Emitを使用してクローンコードを1回だけ生成し、スローリフレクションの代わりにそのコンパイル済みコードを使用できますまたはシリアル化。
自分で問題を見つけて満足のいく解決策を見つけられなかったので、私はそれを行うパッケージを作成することにしました。すべてのタイプで動作し、カスタム記述コードとほぼ同じ速度です。
GitHubでプロジェクトを見つけることができます: https://github.com/marcelltoth/ObjectCloner
NuGetからインストールできます。 ObjectCloner
パッケージを取得して、次のように使用します。
var clone = ObjectCloner.DeepClone(original);
または、拡張機能を使用してオブジェクトタイプを汚染する必要がない場合は、ObjectCloner.Extensions
も取得して、次のように記述します。
var clone = original.DeepClone();
クラス階層のクローン作成の簡単なベンチマークは、Reflectionを使用する場合よりも最大3倍、Newtonsoft.Jsonのシリアル化よりも最大12倍、強く推奨されるBinaryFormatter
よりも約36倍速いパフォーマンスを示しました。
System.Text.Json
を使用:
https://devblogs.Microsoft.com/dotnet/try-the-new-system-text-json-apis/
public static T DeepCopy<T>(this T source)
{
return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}
新しいAPIはSpan<T>
を使用しています。これは高速であるはずであり、いくつかのベンチマークを行うのは良いことです。
注:デフォルトでコレクション値を置き換えるため、Json.NETのようなObjectCreationHandling.Replace
は必要ありません。すべてが新しい公式APIに置き換えられるため、Json.NETについては忘れてください。
これがプライベートフィールドで機能するかどうかはわかりません。
私はそれを行うための新しい方法を見つけましたそれはエミットです。
Emitを使ってILをアプリに追加して実行できます。しかし、私は自分の答えを書くためにこれを完璧にしたいのなら、それが良い方法だとは思わない。
あなたはコードを読むためにいくらかのILを学ぶべきです。プロパティをクラスにコピーできるコードを書きます。
public static class Clone
{
// ReSharper disable once InconsistentNaming
public static void CloneObjectWithIL<T>(T source, T los)
{
//see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
if (CachedIl.ContainsKey(typeof(T)))
{
((Action<T, T>) CachedIl[typeof(T)])(source, los);
return;
}
var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
ILGenerator generator = dynamicMethod.GetILGenerator();
foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
{
//do not copy static that will except
if (temp.GetAccessors(true)[0].IsStatic)
{
continue;
}
generator.Emit(OpCodes.Ldarg_1);// los
generator.Emit(OpCodes.Ldarg_0);// s
generator.Emit(OpCodes.Callvirt, temp.GetMethod);
generator.Emit(OpCodes.Callvirt, temp.SetMethod);
}
generator.Emit(OpCodes.Ret);
var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
CachedIl[typeof(T)] = clone;
clone(source, los);
}
private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}
コードはディープコピーにすることができますが、プロパティをコピーすることはできます。もしあなたがそれをディープコピーにしたいのであれば、ILのためにそれを変更することができます。
すべての答えを読んだ後、私は誰もこの素晴らしいパッケージに言及しなかったことに驚きました
https://github.com/force-net/DeepCloner
READMEについて少し詳しく説明しますが、ここで私たちが仕事でそれを選んだ理由を示します。
免責事項-要件:
- 深いコピーまたは浅いコピーが可能
- ディープクローニングでは、すべてのオブジェクトグラフが維持されます。
- 結果のクローン作成が非常に高速であるため、ランタイムでコード生成を使用します
- 内部構造によってコピーされたオブジェクト、呼び出されたメソッドまたはアクターなし
- 何らかの方法でクラスをマークする必要はありません(Serializable-attributeやインターフェイスの実装など)
- クローン作成のオブジェクトタイプを指定する必要はありません。オブジェクトは、インターフェイスまたは抽象オブジェクトとしてキャストできます(たとえば、intの配列を抽象配列またはIEnumerableとして複製できます。nullでもエラーなしで複製できます)
- クローンされたオブジェクトには、自分がクローンであることを判別する機能がありません(非常に特殊なメソッドを除く)
使い方は簡単です:
var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
基本的に自動コピーコンストラクタを呼び出す必要があるメソッド内でリキャストするだけではどうですか。
T t = new T();
T t2 = (T)t; //eh something like that
List<myclass> cloneum;
public void SomeFuncB(ref List<myclass> _mylist)
{
cloneum = new List<myclass>();
cloneum = (List < myclass >) _mylist;
cloneum.Add(new myclass(3));
_mylist = new List<myclass>();
}
私にはうまくいくようです
これにより、オブジェクトの読み書き可能なすべてのプロパティが別のオブジェクトにコピーされます。
public class PropertyCopy<TSource, TTarget>
where TSource: class, new()
where TTarget: class, new()
{
public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
{
if (src==null) return trg;
if (trg == null) trg = new TTarget();
var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
if (properties != null && properties.Count() > 0)
fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
if (fulllist == null || fulllist.Count() == 0) return trg;
fulllist.ForEach(c =>
{
c.SetValue(trg, c.GetValue(src));
});
return trg;
}
}
そしてこれはあなたがそれをどのように使うかです:
var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
"Creation",
"Description",
"IdTicketStatus",
"IdUserCreated",
"IdUserInCharge",
"IdUserRequested",
"IsUniqueTicketGenerated",
"LastEdit",
"Subject",
"UniqeTicketRequestId",
"Visibility");
またはすべてをコピーする:
var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);