我想做一些事情,比如:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
我想做一些事情,比如:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
当前回答
虽然一种方法是实现ICloneable接口(在这里描述,所以我不会反悔),但这里有一个很好的深度克隆对象复制器,我不久前在代码项目中找到了它,并将其合并到我们的代码中。如其他地方所述,它要求您的对象是可序列化的。
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 via serialization.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>A deep copy of the 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 (ReferenceEquals(source, null)) return default;
using var Stream stream = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
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();。
EDIT(2015年1月10日)我想我会重新考虑这个问题,要说我最近开始使用(Newtonsoft)Json来做这个,它应该更轻,并避免[Serializable]标签的开销。(NB@atconway在评论中指出,私有成员不是使用JSON方法克隆的)
/// <summary>
/// Perform a deep Copy of the object, using Json as a serialization 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 (ReferenceEquals(source, null)) return default;
// 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);
}
其他回答
这将把一个对象的所有可读写财产复制到另一个对象。
public class PropertyCopy<TSource, TTarget>
where TSource: class, new()
where TTarget: class, new()
{
public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
{
if (src==null) return trg;
if (trg == null) trg = new TTarget();
var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
if (properties != null && properties.Count() > 0)
fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
if (fulllist == null || fulllist.Count() == 0) return trg;
fulllist.ForEach(c =>
{
c.SetValue(trg, c.GetValue(src));
});
return trg;
}
}
这是你使用它的方式:
var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
"Creation",
"Description",
"IdTicketStatus",
"IdUserCreated",
"IdUserInCharge",
"IdUserRequested",
"IsUniqueTicketGenerated",
"LastEdit",
"Subject",
"UniqeTicketRequestId",
"Visibility");
或复制所有内容:
var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
我在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);
}
}
我找到了一种新的方法,那就是Emit。
我们可以使用Emit将IL添加到应用程序并运行它。但我认为这不是一个好方法,因为我想完善这一点,所以我写了我的答案。
Emit可以看到官方文件和指南
你应该学习一些IL来阅读代码。我将编写可以在类中复制属性的代码。
public static class Clone
{
// ReSharper disable once InconsistentNaming
public static void CloneObjectWithIL<T>(T source, T los)
{
//see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
if (CachedIl.ContainsKey(typeof(T)))
{
((Action<T, T>) CachedIl[typeof(T)])(source, los);
return;
}
var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
ILGenerator generator = dynamicMethod.GetILGenerator();
foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
{
//do not copy static that will except
if (temp.GetAccessors(true)[0].IsStatic)
{
continue;
}
generator.Emit(OpCodes.Ldarg_1);// los
generator.Emit(OpCodes.Ldarg_0);// s
generator.Emit(OpCodes.Callvirt, temp.GetMethod);
generator.Emit(OpCodes.Callvirt, temp.SetMethod);
}
generator.Emit(OpCodes.Ret);
var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
CachedIl[typeof(T)] = clone;
clone(source, los);
}
private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}
代码可以是深度复制,但它可以复制属性。如果你想让它成为深度副本,你可以改变它,因为IL太难了,我做不到。
我也通过反思看到了它的实现。基本上,有一种方法可以遍历对象的成员,并将它们适当地复制到新对象中。当它到达引用类型或集合时,我认为它对自己进行了递归调用。反射是昂贵的,但效果很好。
C#9.0引入了需要记录的with关键字(谢谢Mark Nading)。这应该允许非常简单的对象克隆(如果需要,还可以进行突变),只需要很少的样板,但只需要一个记录。
您似乎无法通过将类放入通用记录来克隆(按值)类;
using System;
public class Program
{
public class Example
{
public string A { get; set; }
}
public record ClonerRecord<T>(T a)
{
}
public static void Main()
{
var foo = new Example {A = "Hello World"};
var bar = (new ClonerRecord<Example>(foo) with {}).a;
foo.A = "Goodbye World :(";
Console.WriteLine(bar.A);
}
}
这写着“再见世界:()”-字符串是通过引用复制的(不需要)。https://dotnetfiddle.net/w3IJgG
(令人难以置信的是,上面的方法可以正确地用于结构!https://dotnetfiddle.net/469NJv)
但克隆一条记录似乎确实可以按值进行缩进和克隆。
using System;
public class Program
{
public record Example
{
public string A { get; set; }
}
public static void Main()
{
var foo = new Example {A = "Hello World"};
var bar = foo with {};
foo.A = "Goodbye World :(";
Console.WriteLine(bar.A);
}
}
这将返回“Hello World”,字符串是按值复制的!https://dotnetfiddle.net/MCHGEL
更多信息可以在博客文章中找到:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/with-expression