复制不支持复制功能的动态对象的最快方式
本文关键字:复制 方式 动态 不支持 功能 对象 | 更新日期: 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
。因此,如果是这种情况(不仅仅是您的示例代码),这可能是一个严重的"陷阱"。在这种情况下,唯一的解决方案是更改扩展方法的签名,最好是更改其名称。