【问题标题】:Fastest way to copy dynamic object that does not support copy function复制不支持复制功能的动态对象的最快方法
【发布时间】:2012-12-23 00:20:20
【问题描述】:

首先我们可能都同意最好的方法是在自定义对象/实体中实现复制功能。但是考虑一下这种情况。我们没有这个选项,我们也不想编写能够精确复制实体的特定函数,因为实体将来会被更改,所以我们的复制函数会失败。

这是当前实体的简化版本:

[Serializable]
class MyEntity
{
    public MyEntity()
    { 
    }

    public MyEntity(int id, string name)
    {
        this.Id = id;
        this.Name = name; 
    }

    public int Id { get; set; }

    public string Name { get; set; }

    public MyEntity Copy()
    {
        throw new NotImplementedException();
    }
}

为了满足上述所有要求,我想出了两个解决方案:

        //original...
        MyEntity original = new MyEntity() { Id = 1, Name = "demo1" };

        //first way to copy object...
        List<MyEntity> list = new List<MyEntity>() { original};
        MyEntity copy1 = list.ConvertAll(entity => new MyEntity(entity.Id, entity.Name))[0];

        //second way to copy object...
        byte[] bytes = SerializeEntity(original);
        MyEntity copy2 = (MyEntity)DeserializeData(bytes);


    byte[] SerializeEntity(object data)
    {
        byte[] result = null;
        using (MemoryStream ms = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(ms, data);
            result = ms.ToArray();
        }
        return result;
    }

    object DeserializeData(byte[] data)
    {
        object result = null;
        using(MemoryStream ms = new MemoryStream(data))
        {
           BinaryFormatter formatter = new BinaryFormatter();
           result = formatter.Deserialize(ms); 
        }
        return result;
    }

现在是问题。幕后最佳解决方案是什么,为什么是第一还是第二?考虑到上述要求,有没有更好的方法来进行精确复制?将大量复制。

附注: 我知道第一种方式基本上已经是 Honza 指出的复制功能。我有点像序列化和自定义复制功能一样快的东西。

【问题讨论】:

    标签: c# .net optimization copy


    【解决方案1】:

    首先我们可能都同意最好的方法是在自定义对象/实体中实现复制功能。

    我不同意。我讨厌每次都写这样的方法。这是我使用扩展方法的建议:

    public static T Copy<T>(this T obj)
        where T : class
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, obj);
    
            stream.Seek(0, SeekOrigin.Begin);
            return formatter.Deserialize(stream) as T;
        }
    }
    

    这基本上是您的第二个解决方案,但略有优化。无需将 MemoryStream 复制到字节数组中,然后从中创建另一个 MemoryStream。

    最好的是它是通用的,可以用于每个具有[Serializable] 属性的对象。而且我很确定它比您必须访问每个属性的第一个解决方案更快(尽管我没有测量)。

    编辑:

    好的,我现在确实做了一些测量。我对性能的第一个假设是完全错误的!

    我用随机值创建了 1000000 个 MyEntity 对象,然后复制了它们(我还考虑了 Honza Brestan 对深拷贝和浅拷贝的提示):

    使用二进制格式化程序进行深度复制:14.727 秒
    使用 Copy 方法进行深度复制:0.490 s
    带反射的浅拷贝:5.499 秒
    使用 Copy 方法的浅拷贝:0.144 s

    【讨论】:

    • 这类似于我们使用的东西,然而,值得注意的是,每次提供不同的“T”时,编译器都必须生成一个新方法。这将导致轻微的 CPU 开销并使用更多内存。
    • @pescolino 如果对象被标记为可序列化,则使用序列化确实是最防弹的,但它也是最慢的。我真的很想找到一些聪明而快速的方法来复制,但我知道没有“免费午餐”。你总是为了得到其他东西而失去一些东西。
    • @GregorPrimar 阅读Improving performance reflection, what alternatives should I consider,尤其是Jon Skeet's blog,正如他在回答中提到的那样。
    【解决方案2】:

    你可以尝试使用AutoMapper:

    Mapper.CreateMap<MyEntity, MyEntity>();
    
    ...
    
    var copy3 = Mapper.Map<MyEntity, MyEntity>(original);
    

    【讨论】:

    • 我已经阅读了几篇关于 AutoMapper 的帖子,但无法就我的情况所期望的性能做出任何真正的结论。有些人声称这不是所有情况的最佳解决方案。您能否建议任何可能导致使用 AutoMapper 性能不佳的指针? +1 目前可供选择
    【解决方案3】:

    您的第一次尝试与编写自己的 Copy 方法有什么区别?

    public MyEntity Copy()
    {
        return new MyEntity(this.Id, this.Name);
    }
    

    对我来说,这看起来比你的收集尝试更好,无论如何,这完全一样 - 在这两种情况下,你都必须明确命名所有属性。

    如果你不能修改实体类本身,你仍然可以创建一个扩展方法(放置在一个你想使用复制逻辑的地方可见的静态类中)

    public static MyEntity Copy(this MyEntity source)
    {
        return new MyEntity(source.Id, source.Name);
    }
    

    至于第二次尝试,你考虑过两者的不同吗?它们并不完全相同。第一个创建一个副本,而第二个(假设整个对象树是可序列化的)产生一个副本。区别在于它的属性是否也被复制,或者原始对象和它的副本都引用相同的对象。 pescolino 的版本也是如此,顺便说一句,它看起来非常不错。

    所以问题是你想要/需要哪个副本。

    对于真正动态(但可能不太有效)复制方法,我认为您需要使用反射,枚举所有属性并将它们的值从原始对象复制到副本。非完整的演示版可能如下所示:

    public static MyEntity Copy(this MyEntity source)
    {
        var result = new MyEntity();
    
        var properties = source.GetType().GetProperties(
              BindingFlags.Instance | BindingFlags.Public);
    
        foreach (var property in properties)
        {
            var val = property.GetValue(source, null);
            property.SetValue(result, val, null);
        }
    
        return result;
    }
    

    这种方法有其自身的问题,即性能,偶尔需要处理特殊情况(索引器,非公共属性......),但可以完成工作并且也适用于不可序列化的对象。通用版本也很容易实现 - 这取决于您是否需要它。

    另外值得注意的是,因为我和 pescolino 都建议使用扩展方法,所以它们可能存在问题。如果您的实体确实包含与扩展具有相同签名的Copy 方法,编译器将决定使用它而不是扩展。这显然会在调用时抛出NotImplementedException。因此,如果是这种情况(而且不仅仅是您的示例代码),它可能是一个严重的“陷阱”。在这种情况下,唯一的解决方案是更改扩展方法的签名,最好是更改其名称。

    【讨论】:

    • 我同意你的看法,这没什么大的区别。唯一的“亮点”是如果构造函数被更改,我会得到异常。首先,我希望编写尽可能快的选项,创建新实例并设置单独的属性。但是,在这种方法中,如果引入新属性,我不会遇到异常。基本上是在不知情的情况下制作无效副本。您的示例当然涵盖了这个问题。
    猜你喜欢
    • 1970-01-01
    • 2018-04-13
    • 1970-01-01
    • 1970-01-01
    • 2020-03-31
    • 1970-01-01
    • 2011-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多