我想做一些事情,比如:
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();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?
当前回答
创建扩展:
public static T Clone<T>(this T theObject)
{
string jsonData = JsonConvert.SerializeObject(theObject);
return JsonConvert.DeserializeObject<T>(jsonData);
}
这样称呼:
NewObject = OldObject.Clone();
其他回答
深度克隆就是复制状态。对于.net state,表示字段。
假设有一个层次结构:
static class RandomHelper
{
private static readonly Random random = new Random();
public static int Next(int maxValue) => random.Next(maxValue);
}
class A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}
class B : A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}
class C : B
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}
克隆可以通过以下方式完成:
static class DeepCloneExtension
{
// consider instance fields, both public and non-public
private static readonly BindingFlags bindingFlags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
public static T DeepClone<T>(this T obj) where T : new()
{
var type = obj.GetType();
var result = (T)Activator.CreateInstance(type);
do
// copy all fields
foreach (var field in type.GetFields(bindingFlags))
field.SetValue(result, field.GetValue(obj));
// for every level of hierarchy
while ((type = type.BaseType) != typeof(object));
return result;
}
}
演示1:
Console.WriteLine(new C());
Console.WriteLine(new C());
var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");
Console.WriteLine(new C());
Console.WriteLine(new C());
Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");
Console.WriteLine(new C());
Console.WriteLine(new C());
结果:
C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17
Image: C.random = 96 B.random = 18 A.random = 46
C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18
Clone: C.random = 96 B.random = 18 A.random = 46
C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79
注意,所有新对象的随机字段都有随机值,但克隆与图像完全匹配
演示2:
class D
{
public event EventHandler Event;
public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}
// ...
var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");
image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");
image.RaiseEvent();
image.RaiseEvent();
var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");
clone.RaiseEvent();
image.RaiseEvent();
结果:
Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728
注意,事件支持字段也被复制,客户端也订阅了克隆的事件。
由于我在不同的项目中找不到满足我所有需求的克隆器,我创建了一个可以配置并适应不同代码结构的深度克隆器,而不是调整我的代码以满足克隆器的需求。这是通过向要克隆的代码添加注释来实现的,或者您只需保留代码的默认行为。它使用反射、类型缓存并基于fasterflect。对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快。
https://github.com/kalisohn/CloneBehave
也可作为nuget包提供:https://www.nuget.org/packages/Clone.Behave/1.0.0
例如:以下代码将执行deepCloneAddress,但只执行_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 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);
如果您已经在使用第三方应用程序(如ValueInjector或Automapper),可以执行以下操作:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
使用此方法,您不必在对象上实现ISerializable或ICloneable。这在MVC/MVVM模式中很常见,因此已经创建了这样的简单工具。
请参阅GitHub上的ValueInjecter深度克隆示例。
我想你可以试试这个。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it