更快的深度克隆

时间:2009-05-12 10:07:57

标签: c# .net

有没有人想要一个允许我按值.Net对象克隆的框架/类?我只对公共读/写属性(即DataContracts)感兴趣,我不关心引用是否正确解析(即包含相同项目实例的集合两次)。

我尝试通过DataContractSerializer进行序列化技巧(序列化为XML并返回),编写基于反射的克隆类(有时更快/有时更慢),并且想知道是否有人编写了一个可以通过Emit执行此操作的辅助类而不是反思。至于现在发射的IL对我的小脑子来说有点大,但我想这将是最终的解决方案。除非有人知道比DataContractSerializer更快的替代方法。

9 个答案:

答案 0 :(得分:20)

我前段时间为.NET编写了三种深度克隆方法:

  • 一个人使用众所周知的BinaryFormatter技术(虽然我调整了它,因此对象不需要可序列化以便克隆)。这是迄今为止最慢的。

  • 第二次,我使用纯粹的反射。它比使用BinaryFormatter克隆快至少6倍。这个也可用于Silverlight和.NET Compact Framework。

  • 第三个使用Linq表达式树(用于运行时MSIL生成)。它比BinaryFormatter技术快60倍,但遇到每个类的第一次设置时间约为2毫秒。

Logarithmic scale illustrating cloning performance

我在这里发布了所有三种克隆方法作为开源:

http://blog.nuclex-games.com/mono-dotnet/fast-deep-cloning/

答案 1 :(得分:14)

如果您正在谈论对象树/图表:

编写特定的IL以序列化对象是棘手的。 IMO,您最好的选择是查看完整的序列化,例如DataContractSerializer的工作方式 - 但不一定是该引擎。

例如,protobuf-netSerializer.DeepClone<T>方法可能有所帮助。它应该比DataContractSerializer快,至少。目前,您需要为序列化程序添加一些线索(即使只是[ProtoContract(ImplicitFields=ImplicitFields.AllPublic)]) - 但是,当前(不完整)正在进行的工作提供了没有属性的POCO支持。


如果你在谈论个别对象:

在.NET 3.5中使用Expression可以执行相当简单的操作;根据反射构建动态Expression,并调用.Compile()MiscUtil已经有了这个:

DestType clone = PropertyCopy<DestType>.CopyFrom(original);

使用.NET 2.0 / 3.0(不含Expression)时,您可能会考虑HyperDescriptor用于类似目的。

答案 2 :(得分:5)

有很多库可以执行此操作。您可以看到基准测试结果here

简而言之,如果您需要性能,请手动执行,它确实更快。此外,一些库允许执行克隆(通过问题,它是适合您的好方法),这更快。如果您需要任何表现,请不要使用BinaryFormatter

另外,@ frakon提到表达式树的速度与IL Emit相同,略有不正确。表达式树稍慢,但可以在部分受信任的应用程序中使用。

手动13毫秒

DeepCloner(IL Emit)167ms

DeepCloner(表达式)267ms

CloneExtensions(表达式)560毫秒

NClone 901ms

Clone.Behave! 8551ms

GeorgeCloney 1996ms

Nuclex.Cloning n / a(Crashed)

FastDeepCloner 1882ms

BinaryFormatter 15000ms

答案 3 :(得分:4)

IL Emit可能没有在互联网上制作完整的克隆代码。

但是IL Emit与Expression Trees的代码速度相同,因为这两种方法都以类似的编译lambda拷贝函数结束。表达式树比反射快<4倍。最好的事情是 Expression Trees一般克隆功能可在互联网上找到

Cygon表达式树was already mentioned的一个实现。可以在CodeProject文章中找到经过全面测试的新实现 的 Fast Deep Copy by Expression Trees (C#)

使用

的扩展方法
var copy = originalObject.DeepCopyByExpressionTree();

答案 4 :(得分:3)

答案 5 :(得分:3)

我不知道这是否完全符合您的要求,但您也可以使用BinaryFormatter创建深度克隆。请参阅this answer相关问题(Binoj Antony):

public static class GenericCopier<T>
{
    public static T DeepCopy(object objectToCopy)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, objectToCopy);
            memoryStream.Seek(0, SeekOrigin.Begin);
            return (T) binaryFormatter.Deserialize(memoryStream);
        }
    }
}

答案 6 :(得分:1)

基于动态方法的序列化将是最快的。 (使用轻量级codegen生成动态方法并将其用于序列化)

每个属性/字段可以执行1个方法,也可以为整个对象执行一个方法。从我的基准测试中,每个属性执行1次并不会给您带来太多的性能损失。

请参阅以下代码,了解我如何在媒体浏览器中执行此操作:http://code.google.com/p/videobrowser/source/browse/trunk/MediaBrowser/Library/Persistance/Serializer.cs

那里还有一些单元测试。

在指令极限上有快速反射样本,可以完全按照您的要求进行操作。

见:

http://theinstructionlimit.com/?p=76

答案 7 :(得分:0)

CGbR Code Generator可以为您生成ICloneable的实施。您所需要的只是nuget package和实现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[i] = value;
            }
            copy.Numbers = tempNumbers;
        }
        else
        {
            // In a shallow clone only references are copied
            copy.Partials = Partials; 
            copy.Numbers = Numbers; 
        }
        return copy;
    }
}

答案 8 :(得分:-1)

嘛!您可以编写自己的克隆方法,您可以指定忽略或包含属性的属性。 链接中的新库,使用反射和FieldInfo递归克隆对象。 我已将其添加到CodeProject,因此您可以很快访问其代码,您可以根据需要对其进行修改。

快速干净地尝试它,你会喜欢它。
https://www.nuget.org/packages/FastDeepCloner/1.0.1
要么
PM&GT;安装包FastDeepCloner