[C#] ディープクローンオブジェクト


Answers

ほとんどのプリミティブとリストの非常に単純なオブジェクトのためのクローンが欲しかった。 あなたのオブジェクトがシリアル化可能なJSONボックスの外にある場合、このメソッドはトリックを行います。 これには、JSON.NETのようなJSONシリアライザだけで、クローンクラスのインタフェースの変更や実装は必要ありません。

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}
Question

私は次のようなことをしたい:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

そして元のオブジェクトに反映されていない新しいオブジェクトを変更します。

私はこの機能が必要なことはよくありませんので、必要なときには新しいオブジェクトを作成して個々のプロパティを個別にコピーすることに頼っていましたが、より洗練されたより良いやり方状況。

元のオブジェクトに変更が反映されずに複製されたオブジェクトを変更できるように、オブジェクトを複製または完全コピーするにはどうすればよいですか?




As I couldn't find a cloner that meets all my requirements in different projects, I created a deep cloner that can be configured and adapted to different code structures instead of adapting my code to meet the cloners requirements. Its achieved by adding annotations to the code that shall be cloned or you just leave the code as it is to have the default behaviour. It uses reflection, type caches and is based on fasterflect . The cloning process is very fast for a huge amount of data and a high object hierarchy (compared to other reflection/serialization based algorithms).

https://github.com/kalisohn/CloneBehave

Also available as a nuget package: https://www.nuget.org/packages/Clone.Behave/1.0.0

For example: The following code will deepClone Address, but only perform a shallow copy of the _currentJob field.

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



I've seen it implemented through reflection as well. Basically there was a method that would iterate through the members of an object and appropriately copy them to the new object. When it reached reference types or collections I think it did a recursive call on itself. Reflection is expensive, but it worked pretty well.




ここにリンクされているオプションの多くと、この問題の可能な解決策についてたくさんの読書をした後、私はすべてのオプションがIan Pのリンク (すべての他のオプションはそれらのバリエーションです) で要約されていると信じています。質問のコメントagiledeveloper.com/articles/cloning072002.htm

だから私はここで2つの参考文献の関連部分をコピーします。 そうすれば、

cのオブジェクトをクローンするための最善のことはシャープ!

まず第一に、それらはすべて私たちの選択肢です:

Expression TreesによるFast Deep Copy記事では、シリアライゼーション、リフレクション、エクスプレッションツリーによるクローニングのパフォーマンス比較も行っています。

なぜ私がICloneableを選択するの (すなわち手動で)

agiledeveloper.com/articles/cloning072002.htmます。

彼の全記事は、ほとんどの場合、 PersonBrainCityの 3つのオブジェクトを使用して適用可能な例を中心にしています。 私たちは、自分の脳を持ち、同じ都市を持つ人をクローンしたいと思っています。 上記の他の方法のいずれかを使って記事を持ち上げたり読んだりすることもできます。

これは彼の結論を少し修正したものです。

Newを指定してクラス名を指定してオブジェクトをコピーすると、コードが拡張されないことがよくあります。 プロトタイプパターンの応用であるクローンを使用することは、これを達成するより良い方法です。 しかし、C#(およびJava)で提供されているようにクローンを使用することは、かなり問題になる可能性があります。 保護された(非公開の)コピーコンストラクタを提供し、それを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から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

オブジェクト数のカウントを保持すると、ここで実装されたクローンはオブジェクト数の正確なカウントを保持することに注意してください。




次の手順を実行します:

  • Define an ISelf<T> with a read-only Self property that returns T , and ICloneable<out T> , which derives from ISelf<T> and includes a method T Clone() .
  • Then define a CloneBase type which implements a protected virtual generic VirtualClone casting MemberwiseClone to the passed-in type.
  • Each derived type should implement VirtualClone by calling the base clone method and then doing whatever needs to be done to properly clone those aspects of the derived type which the parent VirtualClone method hasn't yet handled.

For maximum inheritance versatility, classes exposing public cloning functionality should be sealed , but derive from a base class which is otherwise identical except for the lack of cloning. Rather than passing variables of the explicit clonable type, take a parameter of type ICloneable<theNonCloneableType> . This will allow a routine that expects a cloneable derivative of Foo to work with a cloneable derivative of DerivedFoo , but also allow the creation of non-cloneable derivatives of Foo .




すべてのパブリックプロパティをコピーする単純な拡張メソッド。 どのオブジェクトでも動作し 、クラスは[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 ) } );
        }
    };
}



Here is a deep copy implementation:

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



Here a solution fast and easy that worked for me without relaying on Serialization/Deserialization.

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

EDIT : requires

    using System.Linq;
    using System.Reflection;

That's How I used it

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}



最善の方法は、次のような拡張メソッドを実装することです。

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }

ソリューション内のどこでも使用できます

var copy = anyObject.DeepClone();

私たちは次の3つの実装を持つことができます:

  1. シリアライゼーション (最短コード)
  2. 反射による - 5倍高速化
  3. 表現木で - 20倍速く

リンクされたメソッドはすべてうまく動作し、深くテストされています。




ValueInjecterAutomapperなどのサードパーティアプリケーションを既に使用している場合は、次のようなことができます:

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

このメソッドを使用すると、オブジェクトにISerializableまたはICloneableを実装する必要はありません。 これはMVC / MVVMパターンと共通しているので、このような簡単なツールが作成されています。

CodePlexのvalueinjecterディープクローニングソリューションを参照してください。




To clone your class object you can use the Object.MemberwiseClone method,

just add this function to your class :

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

then to perform a deep independant copy, just call the DeepCopy method :

yourClass newLine = oldLine.DeepCopy();

お役に立てれば。




簡単な答えは、ICloneableインターフェイスを継承し、.clone関数を実装することです。 クローンは、メンバーシップ・コピーを行い、それを必要とするメンバーすべてに対してディープ・コピーを実行し、結果のオブジェクトを戻す必要があります。 これは再帰的な操作です(複製するクラスのすべてのメンバーが値型であるか、ICloneableを実装し、メンバーが値型であるかICloneableなどを実装する必要があります)。

ICloneableを使用したクローニングの詳細については、 この記事を参照てください

長い答えは "それは依存する"です。 他の人が触れたように、ICloneableはジェネリックではサポートされておらず、循環参照のための特別な考慮が必要であり、実際には.NET Frameworkの「間違い」と見なされます。 シリアライゼーションの方法は、シリアライズ可能なオブジェクトに依存します。シリアライズ可能でない可能性があり、制御できない場合があります。 "最良の"練習があるコミュニティにはまだ多くの議論があります。 現実には、ICloneableのようなすべての状況でベストプラクティスに適合するワンサイズのソリューションはありません。

いくつかのオプションについては、このDeveloper's Cornerの記事を参照してください(Ianへのクレジット)。




私は手動で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を考え出しましたが、無名メソッドブロックの中で収穫が行われないため、不可能です。

さらに良いことに、Generic List <T> clonerを使う:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}



If your Object Tree is Serializeable you could also use something like this

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

be informed that this Solution is pretty easy but it's not as performant as other solutions may be.

And be sure that if the Class grows, there will still be only those fields cloned, which also get serialized.




I like Copyconstructors like that:

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

If you have more things to copy add them




Links