0

コード プロジェクトの関数を使用して、オブジェクトのディープ クローンを作成しています

http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx?msg=3984032#xx3984032xx

ただし、パフォーマンスに敏感なアプリケーションの場合、実行時間の約 10% のパフォーマンス ペナルティがあります。

オブジェクトのコピーを作成し、パフォーマンスの低下を抑える別の方法を提案してもらえますか? 私のオブジェクトは非常に大きく、オブジェクトのリストなどを含むオブジェクトのリストが含まれています。

ありがとう、

ジョセフ

4

4 に答える 4

3

いくつかのアプローチを提案できますが、実装が必ずしも非常に簡単というわけではありません。これを行うために私が個人的に選択する2つのアプローチは次のとおりです。

  1. T4 などのコード生成を使用して、オブジェクト グラフを複製するコードを生成します。T4 は Visual Studio 2008 および Visual Studio 2010 の一部であり、Oleg Sych にはすばらしい T4 ドキュメントがあります: http://www.olegsych.com/2007/12/text-template-transformation-toolkit/

  2. System.Linq.Expression を使用して、オブジェクトを複製するデリゲートを実行時に生成します。一般に、GetValue/SetValue のためにリフレクションは遅くなります。ただし、System.Linq.Expression を使用すると、クラスに対して「ハードコーディング」されたリフレクションからメソッドを生成できます。これらのメソッドをキャッシュすることで、リフレクションの代償を 1 回だけ支払うことができます。

これらのアプローチはどちらも、ディープ クローン ロジックを手作業でコーディングした場合と同等のパフォーマンスを提供するはずです。

ディープ クローニングの作業を複雑にするもの:

  1. インターフェイス フィールド
  2. 抽象クラス フィールド
  3. プライベート コンストラクターを持つクラス (ヘルプについては、http://msdn.microsoft.com/nb-no/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspxを参照してください)
  4. コレクション フィールド

本格的なディープ クローンを作成するのは少し面倒ですが、ドメインを知っていると、問題を単純化できる可能性があります。

PS。私は個人的に System.Linq.Expression よりも T4 を好みます。「魔法」が少ないからです。

于 2011-08-06T11:26:45.653 に答える
1

オブジェクトグラフの装飾を少し受け入れることができる場合は、protobuf-netを使用できます。(たとえば、nugetを使用して取得できます)

ささいな例:

[Serializable]
[ProtoContract]
public class TestObject
{
    [ProtoMember(1)]
    public string TestProperty { get; set; }
}

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.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        Stream stream = new MemoryStream();
        using (stream)
        {
            Serializer.Serialize<T>(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return Serializer.Deserialize<T>(stream);
        }
    }
}

注:Serializerには実際にはこれに適していると思われるDeepCloneメソッドがありますが、Serializeの後にDeserializeを実行するよりも遅いことがわかりました。

更新:マークの質問に関しては、それは非常に奇妙に思えます。これは私の(非常に限られた)テストであり、ディープクローンを使用すると一貫して約30%遅くなるようです。(注:テストを異なる順序で実行し、並行して実行していない場合でも)

    [TestMethod]
    public void TestWithStream()
    {
        var objects = Enumerable.Range(0, 1000000).Select(_ => new TestObject { TestProperty = Guid.NewGuid().ToString() }).ToList();

        Stopwatch w = Stopwatch.StartNew();

        for (int i = 0; i < objects.Count; ++i)
        {
            ObjectCopier.CloneWithStream(objects[i]);
        }
        Console.WriteLine(w.Elapsed);
    }

    [TestMethod]
    public void TestWithDeepClone()
    {
        var objects = Enumerable.Range(0, 1000000).Select(_ => new TestObject { TestProperty = Guid.NewGuid().ToString() }).ToList();

        Stopwatch w = Stopwatch.StartNew();

        for (int i = 0; i < objects.Count; ++i)
        {
            ObjectCopier.CloneWithDeepClone(objects[i]);
        }
        Console.WriteLine(w.Elapsed);
    }

    public static class ObjectCopier
    {
        public static T CloneWithStream<T>(T source)
        {
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable.", "source");
            }

            if (Object.ReferenceEquals(source, null))
            {
                return default(T);
            }

            Stream stream = new MemoryStream();
            using (stream)
            {
                Serializer.Serialize<T>(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return Serializer.Deserialize<T>(stream);
            }
        }

        public static T CloneWithDeepClone<T>(T source)
        {
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable.", "source");
            }

            if (Object.ReferenceEquals(source, null))
            {
                return default(T);
            }

            return Serializer.DeepClone(source);
        }
    }
于 2011-08-06T05:55:48.830 に答える
1

一般的なclrオブジェクトのディープコピーを作成するには、一般的なシリアライザー(BinaryFormatterなど)を使用するか、階層全体に手動コピーを実装する必要があります。BinaryFormatterが遅すぎる場合は、手動シリアル化にフォールバックするか、より高速なフォーマッターを見つけて実装する必要があります。ほとんどのprotobuf実装は、一般的なオブジェクトグラフ(デリゲート、シングルトン、nullコレクションのシリアル化など)ではそのままでは機能しないことに注意してください。したがって、最初にグラフでprotobufのシリアル化が許可されているかどうかを調査します。可能性としては、BinaryFormatterでシリアル化し、可能な場合は特定のサブグラフ(ISerializableを使用して保存)にprotobufsまたは手動のバイナリ書き込みを使用できます。

于 2011-08-06T11:35:34.430 に答える
1

リフレクションを使用して、オブジェクトのすべてのプライベート フィールドを取得できます。オブジェクトのプライベート フィールドをループする関数を作成します。任意の値の型を取り、値をコピーします。オブジェクトが ICloneable インターフェイスをサポートしている場合は、それを呼び出します。クラス内の参照型に対して、このクローン関数を再帰的に呼び出します。

編集、これのコードは次のとおりです。インターネット上のどこかから CloneDictionary を入手したと思いますが、今はどこにあるか覚えていません。また、これを VB.net から C# に変換しました。

  public static object GenericClone(object Obj)
{


object Out = null;
Out = Activator.CreateInstance(Obj.GetType());

Type mytype = Obj.GetType();
while (mytype != null) {

    foreach (System.Reflection.FieldInfo item in mytype.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) {
        object itemValue = item.GetValue(Obj);
        object newvalue = null;
        if (itemValue != null) {
            if (typeof(System.ICloneable).IsAssignableFrom(itemValue.GetType())) {
                newvalue = ((System.ICloneable)itemValue).Clone();
            } else {
                if (itemValue.GetType().IsValueType) {
                    newvalue = itemValue;
                } else {
                    if (itemValue.GetType().Name == "Dictionary`2") {
                        newvalue = DataInterface.CloneDictionary(itemValue);
                    } else if (object.ReferenceEquals(itemValue.GetType(), typeof(System.Text.StringBuilder))) {
                        newvalue = new System.Text.StringBuilder(((System.Text.StringBuilder)itemValue).ToString());
                    } else if (itemValue.GetType().Name == "List`1") {
                        newvalue = DataInterface.CloneList(itemValue);
                    } else {
                        throw (new Exception(item.Name + ", member of " + mytype.Name + " is not cloneable or of value type."));
                    }
                }
            }
        }
        //set new obj copied data
        mytype.GetField(item.Name, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).SetValue(Out, newvalue);
    }
    //must move up to base type, GetFields does not return inherited fields
    mytype = mytype.BaseType;
}

return Out;
}

public static Dictionary<K, V> CloneDictionary<K, V>(Dictionary<K, V> dict)
{
Dictionary<K, V> newDict = null;

// The clone method is immune to the source dictionary being null.
if (dict != null) {
    // If the key and value are value types, clone without serialization.
    if (((typeof(K).IsValueType || object.ReferenceEquals(typeof(K), typeof(string))) && (typeof(V).IsValueType) || object.ReferenceEquals(typeof(V), typeof(string)))) {
        newDict = new Dictionary<K, V>();
        // Clone by copying the value types.
        foreach (KeyValuePair<K, V> kvp in dict) {
            newDict[kvp.Key] = kvp.Value;
        }
    } else {
        newDict = new Dictionary<K, V>();
        // Clone by copying the value types.
        foreach (KeyValuePair<K, V> kvp in dict) {
            newDict[kvp.Key] = DataInterface.GenericClone(kvp.Value);
        }
    }
}

return newDict;
}

public static List<T> CloneList<T>(List<T> list)
{

List<T> Out = new List<T>();
if (typeof(System.ICloneable).IsAssignableFrom(typeof(T))) {
    return (from x in list(T)((ICloneable)x).Clone()).ToList;
} else if (typeof(T).IsValueType) {
    return (from x in list(T)x).ToList;
} else {
    throw new InvalidOperationException("List elements not of value or cloneable type.");
}

}

于 2011-08-06T05:25:10.640 に答える