我想做类似的事情:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要此功能,因此在必要时我会先创建一个新对象,然后分别复制每个属性,但是这总是让我感到有更好或更优雅的处理方式情况。
如何克隆或深度复制对象,以便可以修改克隆的对象而不会在原始对象中反映任何更改?
虽然标准做法是实现ICloneable
接口( 在此进行了介绍,所以我不会反驳),但我还是在一段时间前在The Code Project上发现了一个不错的深克隆对象复印机,并将其合并到我们的资料中。
如在其他地方提到的,它确实要求您的对象可序列化。
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of the object.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", nameof(source));
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
这个想法是先序列化您的对象,然后反序列化为一个新的对象。 这样做的好处是,当对象变得太复杂时,您不必担心克隆所有内容。
并使用扩展方法(也来自最初引用的源):
如果您更喜欢使用C#3.0的新扩展方法 ,请将方法更改为具有以下签名:
public static T Clone<T>(this T source)
{
//...
}
现在,方法调用就变成了objectBeingCloned.Clone();
。
编辑 (2015年1月10日)以为我会重新审视这一点,我最近提到开始使用(Newtonsoft)Json来做到这一点,它应该更轻巧,并且避免了[Serializable]标签的开销。 ( NB @atconway在注释中指出,不使用JSON方法克隆私有成员)
/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
我想要一个克隆器,用于主要是基元和列表的非常简单的对象。 如果您的对象是开箱即用的JSON可序列化的,则此方法可以解决问题。 这不需要修改或实现克隆类上的接口,只需要像JSON.NET这样的JSON序列化程序即可。
public static T Clone<T>(T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
另外,您可以使用此扩展方法
public static class SystemExtension
{
public static T Clone<T>(this T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
}
不使用的原因ICloneable是不是因为它没有一个通用的接口。 不使用它的原因是因为它含糊不清 。 不清楚是要浅拷贝还是要深拷贝。 这取决于实施者。
是的, MemberwiseClone
会进行浅表复制,但是MemberwiseClone
的反面不是Clone
; 可能是DeepClone
,它不存在。 通过对象的ICloneable接口使用对象时,您不知道基础对象执行哪种克隆。 (而且XML注释也不清楚,因为您将获得接口注释,而不是对象的Clone方法中的接口注释。)
我通常所做的只是简单地执行完全符合我想要的Copy
方法。
在对这里链接的许多选项以及该问题的可能解决方案进行了很多阅读之后,我相信所有选项在Ian P的链接上都进行了很好的总结 (所有其他选项都是这些选项的变体),并且最佳解决方案是Pedro77在问题评论上的链接 。
因此,我将仅在此处复制这两个参考的相关部分。 这样我们就可以拥有:
首先,这些都是我们的选择:
文章“通过表达树进行快速深度复制”还对通过序列化,反射和表达树进行克隆的性能进行了比较。
Venkat Subramaniam先生(此处为冗余链接)详细说明了原因 。
他所有的文章都围绕着一个示例,该示例尝试使用3个对象: Person , Brain和City来适用于大多数情况。 我们想要克隆一个人,这个人将拥有自己的大脑,但城市相同。 您可以想象上面任何其他方法可以带来的所有问题或阅读本文。
这是我对他的结论的略微修改:
通过指定
New
后跟类名来复制对象通常会导致代码不可扩展。 使用克隆(原型模式的应用)是一种更好的方法。 但是,使用C#(和Java)提供的克隆也很成问题。 最好提供一个受保护的(非公共)副本构造函数,然后从clone方法中调用它。 这使我们能够将创建对象的任务委派给类本身的实例,从而提供可扩展性,并使用受保护的副本构造函数安全地创建对象。
希望此实现可以使事情变得清晰:
public class Person : ICloneable
{
private final Brain brain; // brain is final since I do not want
// any transplant on it once created!
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
protected Person(Person another)
{
Brain refBrain = null;
try
{
refBrain = (Brain) another.brain.clone();
// You can set the brain in the constructor
}
catch(CloneNotSupportedException e) {}
brain = refBrain;
age = another.age;
}
public String toString()
{
return "This is person with " + brain;
// Not meant to sound rude as it reads!
}
public Object clone()
{
return new Person(this);
}
…
}
现在考虑从Person派生一个类。
public class SkilledPerson extends Person
{
private String theSkills;
public SkilledPerson(Brain aBrain, int theAge, String skills)
{
super(aBrain, theAge);
theSkills = skills;
}
protected SkilledPerson(SkilledPerson another)
{
super(another);
theSkills = another.theSkills;
}
public Object clone()
{
return new SkilledPerson(this);
}
public String toString()
{
return "SkilledPerson: " + super.toString();
}
}
您可以尝试运行以下代码:
public class User
{
public static void play(Person p)
{
Person another = (Person) p.clone();
System.out.println(p);
System.out.println(another);
}
public static void main(String[] args)
{
Person sam = new Person(new Brain(), 1);
play(sam);
SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
play(bob);
}
}
产生的输出将是:
This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e
观察到,如果我们对对象数量进行计数,则此处实现的克隆将对对象数量进行正确计数。
与克隆相比,我更喜欢复制构造函数。 意图更加明确。
复制所有公共属性的简单扩展方法。 适用于任何物体,并且不需要类是[Serializable]
。 可以扩展为其他访问级别。
public static void CopyTo( this object S, object T )
{
foreach( var pS in S.GetType().GetProperties() )
{
foreach( var pT in T.GetType().GetProperties() )
{
if( pT.Name != pS.Name ) continue;
( pT.GetSetMethod() ).Invoke( T, new object[]
{ pS.GetGetMethod().Invoke( S, null ) } );
}
};
}
我在Silverlight中使用ICloneable时遇到了问题,但是我喜欢序列化的想法,因为我可以序列化XML,所以我这样做了:
static public class SerializeHelper
{
//Michael White, Holly Springs Consulting, 2009
//michael@hollyspringsconsulting.com
public static T DeserializeXML<T>(string xmlData) where T:new()
{
if (string.IsNullOrEmpty(xmlData))
return default(T);
TextReader tr = new StringReader(xmlData);
T DocItms = new T();
XmlSerializer xms = new XmlSerializer(DocItms.GetType());
DocItms = (T)xms.Deserialize(tr);
return DocItms == null ? default(T) : DocItms;
}
public static string SeralizeObjectToXML<T>(T xmlObject)
{
StringBuilder sbTR = new StringBuilder();
XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
XmlWriterSettings xwsTR = new XmlWriterSettings();
XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
xmsTR.Serialize(xmwTR,xmlObject);
return sbTR.ToString();
}
public static T CloneObject<T>(T objClone) where T:new()
{
string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
return SerializeHelper.DeserializeXML<T>(GetString);
}
}
我刚刚创建了CloneExtensions
库项目。 它使用Expression Tree运行时代码编译生成的简单赋值操作执行快速,深度的克隆。
如何使用它?
不必编写自己的Clone
或Copy
方法,而是在字段和属性之间分配分配音调,而是让该程序使用Expression Tree自己完成。 标记为扩展方法的GetClone<T>()
方法使您可以在实例上简单地调用它:
var newInstance = source.GetClone();
您可以使用CloningFlags
枚举选择应该从source
复制到newInstance
CloningFlags
:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
可以克隆什么?
以下类/结构成员是内部克隆的:
有多快?
解决方案要快于反射,因为在给定类型T
首次使用GetClone<T>
之前,成员信息仅需收集一次。
当您克隆更多然后耦合相同类型T
实例时,它也比基于序列化的解决方案更快。
和更多...
在文档中阅读有关生成的表达式的更多信息。
List<int>
示例表达式调试列表:
.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
System.Collections.Generic.List`1[System.Int32] $source,
CloneExtensions.CloningFlags $flags,
System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
.Block(System.Collections.Generic.List`1[System.Int32] $target) {
.If ($source == null) {
.Return #Label1 { null }
} .Else {
.Default(System.Void)
};
.If (
.Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
) {
$target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
).Invoke((System.Object)$source)
} .Else {
$target = .New System.Collections.Generic.List`1[System.Int32]()
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
) {
.Default(System.Void)
} .Else {
.Default(System.Void)
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
) {
.Block() {
$target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
$source.Capacity,
$flags,
$initializers)
}
} .Else {
.Default(System.Void)
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
) {
.Block(
System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
System.Collections.Generic.ICollection`1[System.Int32] $var2) {
$var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
$var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
.Loop {
.If (.Call $var1.MoveNext() != False) {
.Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
$var1.Current,
$flags,
$initializers))
} .Else {
.Break #Label2 { }
}
}
.LabelTarget #Label2:
}
} .Else {
.Default(System.Void)
};
.Label
$target
.LabelTarget #Label1:
}
}
与下面的C#代码具有相同的含义:
(source, flags, initializers) =>
{
if(source == null)
return null;
if(initializers.ContainsKey(typeof(List<int>))
target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
else
target = new List<int>();
if((flags & CloningFlags.Properties) == CloningFlags.Properties)
{
target.Capacity = target.Capacity.GetClone(flags, initializers);
}
if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
{
var targetCollection = (ICollection<int>)target;
foreach(var item in (ICollection<int>)source)
{
targetCollection.Add(item.Clone(flags, initializers));
}
}
return target;
}
这与您为List<int>
编写自己的Clone
方法的方式很不一样?
如果您已经使用像一个第三方应用程序ValueInjecter或Automapper ,你可以这样做:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
使用此方法,您不必在对象上实现ISerializable或ICloneable。 这在MVC / MVVM模式中很常见,因此已经创建了这样的简单工具。
简短的答案是您从ICloneable接口继承,然后实现.clone函数。 克隆应该执行成员级复制,并在需要复制的任何成员上执行深层复制,然后返回结果对象。 这是一个递归操作(它要求您要克隆的类的所有成员均为值类型或实现ICloneable,并且其成员为值类型或实现ICloneable,依此类推)。
有关使用ICloneable进行克隆的更详细说明,请参阅本文 。
长的答案是“取决于”。 正如其他人提到的那样,泛型不支持ICloneable,ICloneable需要对循环类引用进行特殊考虑,并且在某些情况下,ICloneable实际上在.NET Framework中被视为“错误” 。 序列化方法取决于您的对象是否可序列化,它们可能不是,并且您可能无法控制。 关于“最佳”实践,社区中仍有很多争论。 实际上,所有解决方案都不是一种适用于所有情况的最佳解决方案,例如最初解释为ICloneable的情况。
有关更多选择(请参阅Ian),请参见此开发人员专栏文章 。
干杯。
编辑:项目已终止
如果您想将克隆真正克隆为未知类型,可以看看fastclone 。
这是基于表达式的克隆,其工作速度比二进制序列化大约快10倍,并且可以保持完整的对象图完整性。
这意味着:如果您多次引用层次结构中的同一对象,则克隆还将引用一个实例beeing。
无需对要克隆的对象进行接口,属性或任何其他修改。
保持简单,并使用AutoMapper,就像其他提到的一样,它是一个简单的小库,用于将一个对象映射到另一个对象。要将对象复制到具有相同类型的另一个对象,您只需要三行代码:
MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);
现在,目标对象是源对象的副本。 还不够简单? 创建一个扩展方法以在解决方案中的任何地方使用:
public static T Copy<T>(this T source)
{
T copy = default(T);
Mapper.CreateMap<T, T>();
copy = Mapper.Map<T, T>(source);
return copy;
}
通过使用扩展方法,三行变成一行:
MyType copy = source.Copy();
我想出了这个方法来克服.NET缺点,即必须手动深复制List <T>。
我用这个:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
foreach (SpotPlacement sp in spotPlacements)
{
yield return (SpotPlacement)sp.Clone();
}
}
在另一个地方:
public object Clone()
{
OrderItem newOrderItem = new OrderItem();
...
newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
...
return newOrderItem;
}
我试图提出一个做到这一点的oneliner,但是这是不可能的,因为yield无法在匿名方法块中工作。
更好的是,使用通用List <T>克隆器:
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}
换句话说, 除非您有需要解决的性能瓶颈,并且可以使用事件探查器来证明,否则请选择另一个答案 。
执行深度克隆的以下方法是:
为了获得最高速度,您可以使用Nested MemberwiseClone进行深度复制 。 它的速度几乎与复制值结构相同,并且比(a)反射或(b)序列化要快得多(如本页其他答案所述)。
请注意, 如果您使用Nested MemberwiseClone进行深层复制 ,则必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有上述ShallowCopy方法来创建完整的克隆。 这很简单:总共只有几行,请参见下面的演示代码。
以下是代码输出,显示了100,000个克隆的相对性能差异:
在类上使用Nested MemberwiseClone几乎与复制结构一样快,并且复制结构几乎与.NET所能达到的理论最大速度相当。
Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:04.7795670,30000000
Demo 2 of shallow and deep copy, using structs and value copying:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details:
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:01.0875454,30000000
Demo 3 of deep copy, using class and serialize/deserialize:
Elapsed time: 00:00:39.9339425,30000000
要了解如何使用MemberwiseCopy进行深度复制,以下是用于生成上述时间的演示项目:
// Nested MemberwiseClone example.
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
public Person(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
[Serializable] // Not required if using MemberwiseClone
public class PurchaseType
{
public string Description;
public PurchaseType ShallowCopy()
{
return (PurchaseType)this.MemberwiseClone();
}
}
public PurchaseType Purchase = new PurchaseType();
public int Age;
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person DeepCopy()
{
// Clone the root ...
Person other = (Person) this.MemberwiseClone();
// ... then clone the nested class.
other.Purchase = this.Purchase.ShallowCopy();
return other;
}
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
public PersonStruct(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
public struct PurchaseType
{
public string Description;
}
public PurchaseType Purchase;
public int Age;
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct ShallowCopy()
{
return (PersonStruct)this;
}
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct DeepCopy()
{
return (PersonStruct)this;
}
}
// Added only for a speed comparison.
public class MyDeepCopy
{
public static T DeepCopy<T>(T obj)
{
object result = null;
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
result = (T)formatter.Deserialize(ms);
ms.Close();
}
return (T)result;
}
}
然后,从main调用演示:
void MyMain(string[] args)
{
{
Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
var Bob = new Person(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
}
{
Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
var Bob = new PersonStruct(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details:\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
}
{
Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
int total = 0;
var sw = new Stopwatch();
sw.Start();
var Bob = new Person(30, "Lamborghini");
for (int i = 0; i < 100000; i++)
{
var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
total += BobsSon.Age;
}
Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
Console.ReadKey();
}
再次,请注意, 如果您将Nested MemberwiseClone用于深层副本 ,则必须为类中的每个嵌套级别手动实现ShallowCopy,并且必须通过DeepCopy调用所有上述ShallowCopy方法来创建完整的副本。 这很简单:总共只有几行,请参见上面的演示代码。
请注意,在克隆对象时,“ struct ”和“ class ”之间有很大的区别:
查看值类型和引用类型之间的差异 。
此代码的一个很好的用例是将嵌套类或结构的克隆馈入队列,以实现生产者/使用者模式。
ConcurrentQueue
。 这在实践中非常有效,并且使我们能够将许多线程(生产者)与一个或多个线程(消费者)分离。
而且这种方法也非常快:如果使用嵌套结构,它比对嵌套类进行序列化/反序列化要快35倍,并且使我们能够利用计算机上可用的所有线程。
显然,ExpressMapper的速度甚至比上述手工编码还快。 我可能必须看一下它们与探查器的比较。
由于在不同的项目中找不到能满足我所有要求的克隆器,因此我创建了一个深度克隆器,可以对它进行配置和调整以适应不同的代码结构,而不用修改我的代码来满足克隆器的要求。 通过在将要克隆的代码中添加注释或通过保留代码以使其具有默认行为来实现此目的。 它使用反射,类型缓存,并且基于fastflect 。 对于大量数据和高对象层次结构,克隆过程非常快(与其他基于反射/序列化的算法相比)。
https://github.com/kalisohn/CloneBehave
也可以作为nuget包提供: https ://www.nuget.org/packages/Clone.Behave/1.0.0
例如:以下代码将DeepClone Address,但仅执行_currentJob字段的浅表副本。
public class Person
{
[DeepClone(DeepCloneBehavior.Shallow)]
private Job _currentJob;
public string Name { get; set; }
public Job CurrentJob
{
get{ return _currentJob; }
set{ _currentJob = value; }
}
public Person Manager { get; set; }
}
public class Address
{
public Person PersonLivingHere { get; set; }
}
Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");
Address adrClone = adr.Clone();
//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
我也看到它是通过反射实现的。 基本上,有一种方法可以遍历对象的成员并将其适当地复制到新对象。 当它到达引用类型或集合时,我认为它对自己进行了递归调用。 反射很昂贵,但是效果很好。
这是一个深层复制实现:
public static object CloneObject(object opSource)
{
//grab the type and create a new instance of that type
Type opSourceType = opSource.GetType();
object opTarget = CreateInstanceOfType(opSourceType);
//grab the properties
PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
//iterate over the properties and if it has a 'set' method assign it from the source TO the target
foreach (PropertyInfo item in opPropertyInfo)
{
if (item.CanWrite)
{
//value types can simply be 'set'
if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
{
item.SetValue(opTarget, item.GetValue(opSource, null), null);
}
//object/complex types need to recursively call this method until the end of the tree is reached
else
{
object opPropertyValue = item.GetValue(opSource, null);
if (opPropertyValue == null)
{
item.SetValue(opTarget, null, null);
}
else
{
item.SetValue(opTarget, CloneObject(opPropertyValue), null);
}
}
}
}
//return the new item
return opTarget;
}
通常,您可以实现ICloneable接口并自己实现Clone。 C#对象具有内置的MemberwiseClone方法,该方法执行浅表复制,可以帮助您解决所有原语。
对于深层副本,它无法知道如何自动执行。
这种方法为我解决了问题:
private static MyObj DeepCopy(MyObj source)
{
var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);
}
像这样使用它: MyObj a = DeepCopy(b);
我喜欢这样的Copyconstructors:
public AnyObject(AnyObject anyObject)
{
foreach (var property in typeof(AnyObject).GetProperties())
{
property.SetValue(this, property.GetValue(anyObject));
}
foreach (var field in typeof(AnyObject).GetFields())
{
field.SetValue(this, field.GetValue(anyObject));
}
}
如果您还有其他要复制的内容,请添加它们
从序列化到手动实现到反思,我们已经看到了很多想法,我想使用CGbR代码生成器提出一种完全不同的方法。 生成克隆方法具有内存和CPU效率,因此比标准DataContractSerializer快300倍。
您所需ICloneable
就是使用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.Add(value);
}
copy.Numbers = tempNumbers;
}
else
{
// In a shallow clone only references are copied
copy.Partials = Partials;
copy.Numbers = Numbers;
}
return copy;
}
}
注意:最新版本具有更多的null检查,但为了更好的理解,我将其省略。
在这里,这种快速简便的解决方案对我有用,而无需依靠序列化/反序列化。
public class MyClass
{
public virtual MyClass DeepClone()
{
var returnObj = (MyClass)MemberwiseClone();
var type = returnObj.GetType();
var fieldInfoArray = type.GetRuntimeFields().ToArray();
foreach (var fieldInfo in fieldInfoArray)
{
object sourceFieldValue = fieldInfo.GetValue(this);
if (!(sourceFieldValue is MyClass))
{
continue;
}
var sourceObj = (MyClass)sourceFieldValue;
var clonedObj = sourceObj.DeepClone();
fieldInfo.SetValue(returnObj, clonedObj);
}
return returnObj;
}
}
编辑 :要求
using System.Linq;
using System.Reflection;
那就是我的使用方式
public MyClass Clone(MyClass theObjectIneededToClone)
{
MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
我想你可以试试看。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
跟着这些步骤:
Self
属性的ISelf<T>
,该属性返回T
,以及ICloneable<out T>
,后者从ISelf<T>
派生并包括方法T Clone()
。 CloneBase
类型,该类型实现一个protected virtual generic VirtualClone
将MemberwiseClone
强制转换为传入的类型。 VirtualClone
,然后执行所需的任何操作以正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。 为了最大程度地继承通用性,应将公开公共克隆功能的类sealed
,但应从基类派生,否则除非缺少克隆,否则基类是相同的。 而不是传递显式可克隆类型的变量,而是使用类型为ICloneable<theNonCloneableType>
的参数。 这将允许期望Foo
可DerivedFoo
派生类与DerivedFoo
派生类一起工作的DerivedFoo
,还允许创建Foo
的非克隆派生类。
我创建了一个可接受的答案版本,它与“ [Serializable]”和“ [DataContract]”一起使用。 自编写以来已经有一段时间了,但是如果我没记错的话,[DataContract]需要一个不同的序列化器。
需要System,System.IO,System.Runtime.Serialization,System.Runtime.Serialization.Formatters.Binary,System.Xml ;
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (typeof(T).IsSerializable == true)
{
return CloneUsingSerializable<T>(source);
}
if (IsDataContract(typeof(T)) == true)
{
return CloneUsingDataContracts<T>(source);
}
throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
}
/// <summary>
/// Perform a deep Copy of an object that is marked with '[Serializable]'
/// </summary>
/// <remarks>
/// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
/// Uses code found on CodeProject, which allows free use in third party apps
/// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// </remarks>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneUsingSerializable<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
/// <summary>
/// Perform a deep Copy of an object that is marked with '[DataContract]'
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneUsingDataContracts<T>(T source)
{
if (IsDataContract(typeof(T)) == false)
{
throw new ArgumentException("The type must be a data contract.", "source");
}
// ** Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
DataContractSerializer dcs = new DataContractSerializer(typeof(T));
using(Stream stream = new MemoryStream())
{
using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
{
dcs.WriteObject(writer, source);
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
{
return (T)dcs.ReadObject(reader);
}
}
}
}
/// <summary>
/// Helper function to check if a class is a [DataContract]
/// </summary>
/// <param name="type">The type of the object to check.</param>
/// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
public static bool IsDataContract(Type type)
{
object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
return attributes.Length == 1;
}
}
要克隆您的类对象,可以使用Object.MemberwiseClone方法,
只需将此函数添加到您的班级:
public class yourClass
{
// ...
// ...
public yourClass DeepCopy()
{
yourClass othercopy = (yourClass)this.MemberwiseClone();
return othercopy;
}
}
然后执行深层独立副本,只需调用DeepCopy方法:
yourClass newLine = oldLine.DeepCopy();
希望这可以帮助。
如果您的对象树是可序列化的,那么您也可以使用类似的东西
static public MyClass Clone(MyClass myClass)
{
MyClass clone;
XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
using (var ms = new MemoryStream())
{
ser.Serialize(ms, myClass);
ms.Position = 0;
clone = (MyClass)ser.Deserialize(ms);
}
return clone;
}
知道此解决方案非常简单,但性能不如其他解决方案高。
并且请确保如果Class增加,那么仍然只会克隆那些字段,并且这些字段也会被序列化。
好的,本文中有一些明显的反射示例,但反射通常很慢,直到您开始正确缓存它为止。
如果将其正确缓存,则它将深度克隆1,000000对象4,6s(由Watcher测量)。
static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
比您采用缓存的属性或将新属性添加到字典并简单地使用它们
foreach (var prop in propList)
{
var value = prop.GetValue(source, null);
prop.SetValue(copyInstance, value, null);
}
完整代码检查我的帖子中的另一个答案