复制不支持复制功能的动态对象的最快方式

本文关键字:复制 方式 动态 不支持 功能 对象 | 更新日期: 2023-09-27 17:49:13

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

下面是当前实体的简化版本:

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

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

PS注意:

我知道第一种方式基本上已经是一个复制功能,正如Honza指出的那样。我想要一些通用的东西,比如序列化和接近自定义复制函数的速度

复制不支持复制功能的动态对象的最快方式

首先,我们可能都同意最好的方法是在自定义对象/实体中实现Copy函数。

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

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 s
deep copy with copy method: 0.490 s
带反射的浅拷贝:5.499 s
使用copy方法进行浅拷贝:0.144 s

您可以尝试使用AutoMapper:

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

第一次尝试和自己编写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。因此,如果是这种情况(不仅仅是您的示例代码),这可能是一个严重的"陷阱"。在这种情况下,唯一的解决方案是更改扩展方法的签名,最好是更改其名称。