0

我正在使用代码项目中的函数来深度克隆我的对象

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

但是对于性能敏感的应用程序,我的性能损失约为执行时间的 10%。

有人可以向我建议另一种创建对象副本并降低性能损失的方法吗?我的对象非常大,包含对象列表,这些对象列表又包含对象列表等。

谢谢,

约瑟夫

4

4 回答 4

3

我可以提出几种方法,但它们实施起来并不一定超级简单。我个人选择的两种方法是:

  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. 接口字段
  2. 抽象类字段
  3. 具有私有构造函数的类(有关帮助,请参见http://msdn.microsoft.com/nb-no/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx
  4. 集合字段

编写一个成熟的深度克隆有点麻烦,但是当你知道你的领域时,你也许可以对这个问题做一些简化。

PS。我个人更喜欢 T4 而不是 System.Linq.Expression 因为那不那么“神奇”

于 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 方法,看起来很适合这个,但我发现它比先序列化再反序列化要慢。

更新:关于马克的问题,这似乎很奇怪。这是我的(非常有限的)测试,使用深度克隆似乎始终慢约 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

您可以使用反射来获取对象的所有私有字段。创建一个函数来遍历对象的私有字段。获取任何值类型并复制该值。如果对象支持 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 回答
1

如果不使用通用序列化程序(例如 BinaryFormatter)或为整个层次结构实现手动复制,则无法创建通用 clr 对象的深层副本。如果 BinaryFormatter 太慢,您必须退回到手动序列化,或者找到/实现更快的格式化程序。请注意,大多数 protobuf 实现不适用于通用对象图(序列化委托、单例、空集合......)。因此,首先调查您的图是否允许 protobuf 序列化,可能您可以使用 BinaryFormatter 进行序列化,并在可能的情况下对某些子图(使用 ISerializable 存储)使用 protobufs 或手动二进制写入

于 2011-08-06T11:35:34.430 回答