.NET Framework 中克隆对象 - 第一部分






4.61/5 (26投票s)
.NET Framework 中克隆对象的重要性
引言
在本文中,我们将展示在 .NET Framework 中克隆对象的多种方法。 每种克隆模式都会分析其优缺点。
此说明不适用于不可变类(字符串、委托、结构等),因为这些类具有其他行为,并且不在本文中讨论。
为此,我们将使用两种实现技术,首先是 ICloneable Interface
,其次是依赖于克隆类型的扩展方法。
背景
这是一个非常重要的程序部分。 如果您不是基本开发人员,则可以跳过此部分.
在高级语言(C#、Java、C++ 等)中,当我们把一个对象赋值给另一个对象时,我们将两个对象赋值给同一个引用。
Customer customer1 = new Customer { ID = 1, Name = "Test", City = "City", Sales = 1000m };
Customer customer2 = customer1;
Customer1
和 Customer2
被链接,并且一个对象中的任何修改都将反映在另一个对象中。
克隆对于取消对象及其虚拟副本的链接是必要的,并且它们是独立的对象。
Customer customer2 = (Customer)customer1.Clone();
示例类
这是我们将用于示例的类
public class Customer : ICloneable
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Sales { get; set; }
public DateTime EntryDate { get; set; }
public Address Adress { get; set; }
public Collection<string> Mails { get; set; }
protected string Data1 { get; set; }
private string Data2 { get; set; }
public Customer()
{
Data1 = "data1";
Data2 = "Data2";
}
public virtual object Clone() { }
}
ICloneable
它是用于克隆对象的官方 .NET Framework 接口。 它非常简单,只有一个方法 Clone
。
这个接口让您可以随意使用 Clone
方法。 我们可以应用我们选择的任何深度级别。
public interface ICloneable
{
object Clone();
}
这个 interface
最大的问题是 Clone
方法的返回值,对象类型。 每当您使用 Clone
方法时,您都必须进行强制转换为主要类型
Customer customer2 = (Customer)customer1.Clone();
扩展方法
克隆对象的另一种方法是使用扩展方法。 这些方法提供了返回泛型类型的机会,这样我们就可以避免装箱/拆箱问题。 我们只编写一次扩展方法,并且我们的扩展方法扩展了对象,我们可以将其用于所有 .NET 类型。
public static class MyExtensions
{
public static T CloneObject<T>(this object source)
{
T result = Activator.CreateInstance<T>();
//// **** made things
return result;
}
}
呼叫
Customer Customer2 = customer1.CloneObject();
我们可以将扩展方法与 ICloneable
结合使用
public class Customer : ICloneable
{
// Properties ...
public virtual object Clone()
{
return this.CloneObject();
}
}
Object.MemberWiseClone
MemberWiseClone
是对象的 protected
方法。 此方法创建当前对象到新对象的浅表副本。
MemberWiseClone
以不同的方式复制引用属性(类)或值属性(结构)
- 结构 - 逐位复制属性的值
- 类 – 复制属性的引用,因此它们是相同的对象
类的情况是一个问题,因为两个对象是相同的。 这是该方法的缺点。
MemberWiseClone
的使用通常与 ICloneable Interface
相同,因为 MemberWiseClone
是一个 protected
方法,并且必须在内部调用它。
MemberWiseClone
示例
public class Customer : ICloneable
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Sales { get; set; }
public DateTime EntryDate { get; set; }
public Address Adress { get; set; }
public Collection<string> Mails { get; set; }
protected string Data1 { get; set; }
private string Data2 { get; set; }
public Customer()
{
Data1 = "data1";
Data2 = "Data2";
}
public virtual object Clone()
{
return this.MemberwiseClone();
}
}
优点
- 易于开发
- 代码量很少
- 易于理解
- 它复制任何字段/属性类型(简单和复杂)
- 它不需要用任何特殊属性标记类
缺点
- 它只能在类内部调用,因为它是一个
protected
方法。 - 必须在所有要克隆的类中实现它。
- 要克隆的对象的引用属性不会被复制,它们是被链接的。
clone
方法返回对象,因此每次使用它时,我们都必须进行强制转换。
如果我们尝试完全深度复制,我们必须手动分配所有引用属性
public virtual object Clone()
{
var result = this.MemberwiseClone();
// Manual assignments
result.Adress = new Address
{
City = this.Adress.City,
Street = this.Adress.Street,
ZipCode = this.Adress.ZipCode
};
result.Mails = new Collection<string>();
this.Mails.ToList().ForEach(a => result.Mails.Add(a));
return result;
}
流 - 格式化程序
这种克隆类型使用序列化来处理对象副本。 它进行深度复制,但强制使用任何序列化属性标记类对象。
在这个站点中,我们的同伴 Surajit Datta 有一个很好的例子 文章,我们将使用此代码作为我们的示例。
为了避免在所有克隆类中编写此代码,我们将创建一个扩展方法
public static T CloneObjectSerializable<T>(this T obj) where T : class
{
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
object result = bf.Deserialize(ms);
ms.Close();
return (T) result;
}
呼叫
Customer customer2 = customer1.CloneObjectSerializable();
如果您运行此代码,它会抛出 SerializationException
为了防止这些错误,我们用 Serialization 属性标记该类
[Serializable]
public class Customer
优点
- 易于开发
- 易于在扩展方法中编写,因此我们只需实现一次
- 它复制任何字段/属性类型(简单和复杂)
- 它实现深度复制
- 它不需要在类内部调用,因为它是对象扩展方法
- 返回泛型类型,因此我们不必应用装箱/拆箱
缺点
- 它需要用特殊属性标记
- 对于您的实现,它需要更多的代码和逻辑
- 内存泄漏(感谢 deverton bezerra)
此方法可以与 ICloneable
完美配合使用,并保持其所有优点。
public virtual object Clone()
{
return this.CloneObjectSerializable();
}
结论
在 .NET Framework 中没有克隆对象的魔法方法,但这两种模型使工作更容易。 在开发世界中,必须清楚地了解克隆对象。 这种误解通常是我们程序中出现错误和意外行为的后果。