c# clone 実装 - ディープクローンオブジェクト



15 Answers

ほとんどのプリミティブとリストの非常に単純なオブジェクトのためのクローンが欲しかった。 あなたのオブジェクトがシリアル化可能な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);
    }
}
deepcopy シリアライズ ディープコピー

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

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

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

私はしばしばこの機能が必要ではないので、必要なときには新しいオブジェクトを作成して個々のプロパティを個別にコピーすることに頼っていましたが、それは常により良いまたはよりエレガントなハンドリング方法状況。

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




ここにリンクされているオプションの多くと、この問題の可能な解決策についてたくさんの読書をした後、私はすべてのオプションが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

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




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



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

MyObject oldObj; // The existing object to clone

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

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

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




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

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

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

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




  1. 基本的には、ICloneableインターフェイスを実装し、オブジェクト構造のコピーを実現する必要があります。
  2. それがすべてのメンバーの深いコピーであれば、すべての子供がクローン可能であることを保証する必要があります。
  3. 場合によっては、このプロセス中にいくつかの制限があることを認識する必要がある場合があります。たとえば、ORMオブジェクトをコピーする場合、ほとんどのフレームワークではセッションに1つのオブジェクトのみが許可され、このオブジェクトのクローンを作成してはいけません。これらのオブジェクトのセッションアタッチについて

乾杯。




シンプルでAutoMapperを他のものとAutoMapperように使用すると、あるオブジェクトを別のオブジェクトにマップする簡単なライブラリです...同じタイプの別のオブジェクトにオブジェクトをコピーするには、3行のコードが必要です。

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



一般的には、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;
}



異なるプロジェクトですべての要件を満たすクローナを見つけることができなかったので、私はクローンの要件を満たすために自分のコードを適合させるのではなく、異なるコード構造に構成して適用できる深いクローンを作成しました。これは、コードに注釈を追加することによって実現されます。注釈は、クローンされるコード、またはデフォルトの動作をそのままそのまま残します。それは反射、タイプキャッシュを使用し、fasterflect基づいていfasterflect。クローニングプロセスは、膨大な量のデータと高いオブジェクト階層(他のリフレクション/シリアライゼーションベースのアルゴリズムと比較して)では非常に高速です。

https://github.com/kalisohn/CloneBehave

また、ナゲットパッケージとしても利用できます:https://www.nuget.org/packages/Clone.Behave/1.0.0 ://www.nuget.org/packages/Clone.Behave/1.0.0https://www.nuget.org/packages/Clone.Behave/1.0.0

例:次のコードは、deepClone Addressですが、_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



私はそのような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));
        }
    }

追加するものがあれば追加してください




シリアライゼーション/デシリアライゼーションを中継することなく、簡単で簡単に解決できるソリューションです。

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



クラスオブジェクトを複製するには、Object.MemberwiseCloneメソッドを使用します。

この関数をクラスに追加するだけです:

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

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

深い独立したコピーを実行するには、DeepCopyメソッドを呼び出します。

yourClass newLine = oldLine.DeepCopy();

お役に立てれば。




オブジェクトツリーがSerializeableの場合は、次のようなものを使用することもできます

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

このソリューションはかなり簡単ですが、他のソリューションほどパフォーマンスが悪いとの情報があります。

また、クラスが成長すると、クローン化されたフィールドだけが存在し、シリアル化されることも確認してください。




Related


Tags

c#   .net   clone