[英]I need to implement C# deep copy constructors with inheritance. What patterns are there to choose from?
我希望在C#中实现我的类层次结构的深层复制
public Class ParentObj : ICloneable
{
protected int myA;
public virtual Object Clone ()
{
ParentObj newObj = new ParentObj();
newObj.myA = theObj.MyA;
return newObj;
}
}
public Class ChildObj : ParentObj
{
protected int myB;
public override Object Clone ( )
{
Parent newObj = this.base.Clone();
newObj.myB = theObj.MyB;
return newObj;
}
}
这与克隆“仅子级的孩子”时不起作用。 在我的代码中,某些类具有较大的层次结构。
建议这样做的方法是什么? 克隆每个级别的所有内容而不调用基类似乎是错误的? 对于这个问题必须有一些巧妙的解决方案,它们是什么?
我可以感谢大家的回答。 看到其中一些方法真的很有趣。 我认为,如果有人举一个完整的反思性回答的例子,那会很好。 +1等待中!
典型的方法是在C ++中使用“复制构造函数”模式:
class Base : ICloneable
{
int x;
protected Base(Base other)
{
x = other.x;
}
public virtual object Clone()
{
return new Base(this);
}
}
class Derived : Base
{
int y;
protected Derived(Derived other)
: Base(other)
{
y = other.y;
}
public override object Clone()
{
return new Derived(this);
}
}
另一种方法是在Clone
的实现中使用Object.MemberwiseClone
这将确保结果始终是正确的类型,并将允许覆盖扩展:
class Base : ICloneable
{
List<int> xs;
public virtual object Clone()
{
Base result = this.MemberwiseClone();
// xs points to same List object here, but we want
// a new List object with copy of data
result.xs = new List<int>(xs);
return result;
}
}
class Derived : Base
{
List<int> ys;
public override object Clone()
{
// Cast is legal, because MemberwiseClone() will use the
// actual type of the object to instantiate the copy.
Derived result = (Derived)base.Clone();
// ys points to same List object here, but we want
// a new List object with copy of data
result.ys = new List<int>(ys);
return result;
}
}
两种方法都要求层次结构中的所有类都遵循该模式。 使用哪个是优先选择的问题。
如果您只有任何实现ICloneable
随机类而不保证实现(除了遵循记录的ICloneable
语义),就无法扩展它。
尝试序列化技巧:
public object Clone(object toClone)
{
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms= new MemoryStream();
bf.Serialize(ms, toClone);
ms.Flush();
ms.Position = 0;
return bf.Deserialize(ms);
}
使用此代码时应格外小心。 使用风险自负。 本示例按原样提供,不提供任何形式的保证。
还有另一种方法可以对对象图执行深度克隆。 在考虑使用此示例时,请注意以下几点很重要:
缺点:
那为什么要使用它呢?
优点:
代码用法:
您只需用一个对象调用它:
Class1 copy = Clone(myClass1);
或假设您有一个子对象,并且您已订阅了该对象的事件...现在您想克隆该子对象。 通过提供不克隆对象的列表,可以保留部分对象图:
Class1 copy = Clone(myClass1, this);
实现方式:
现在,让我们首先简单地解决问题...这是切入点:
public static T Clone<T>(T input, params object[] stableReferences)
{
Dictionary<object, object> graph = new Dictionary<object, object>(new ReferenceComparer());
foreach (object o in stableReferences)
graph.Add(o, o);
return InternalClone(input, graph);
}
现在已经足够简单了,它只是在克隆过程中为对象建立一个字典映射,并用不应克隆的任何对象填充它。 您会注意到提供给字典的比较器是ReferenceComparer,让我们看一下它的作用:
class ReferenceComparer : IEqualityComparer<object>
{
bool IEqualityComparer<object>.Equals(object x, object y)
{ return Object.ReferenceEquals(x, y); }
int IEqualityComparer<object>.GetHashCode(object obj)
{ return RuntimeHelpers.GetHashCode(obj); }
}
这很容易,只需一个比较器就可以强制使用System.Object的get哈希和引用相等...现在来了:
private static T InternalClone<T>(T input, Dictionary<object, object> graph)
{
if (input == null || input is string || input.GetType().IsPrimitive)
return input;
Type inputType = input.GetType();
object exists;
if (graph.TryGetValue(input, out exists))
return (T)exists;
if (input is Array)
{
Array arItems = (Array)((Array)(object)input).Clone();
graph.Add(input, arItems);
for (long ix = 0; ix < arItems.LongLength; ix++)
arItems.SetValue(InternalClone(arItems.GetValue(ix), graph), ix);
return (T)(object)arItems;
}
else if (input is Delegate)
{
Delegate original = (Delegate)(object)input;
Delegate result = null;
foreach (Delegate fn in original.GetInvocationList())
{
Delegate fnNew;
if (graph.TryGetValue(fn, out exists))
fnNew = (Delegate)exists;
else
{
fnNew = Delegate.CreateDelegate(input.GetType(), InternalClone(original.Target, graph), original.Method, true);
graph.Add(fn, fnNew);
}
result = Delegate.Combine(result, fnNew);
}
graph.Add(input, result);
return (T)(object)result;
}
else
{
Object output = FormatterServices.GetUninitializedObject(inputType);
if (!inputType.IsValueType)
graph.Add(input, output);
MemberInfo[] fields = inputType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
object[] values = FormatterServices.GetObjectData(input, fields);
for (int i = 0; i < values.Length; i++)
values[i] = InternalClone(values[i], graph);
FormatterServices.PopulateObjectMembers(output, fields, values);
return (T)output;
}
}
您会立即注意到数组和委托复制的特殊情况。 每个都有其自己的原因,首先Array没有可克隆的“成员”,因此您必须处理此问题并依赖浅Clone()成员,然后克隆每个元素。 至于代表,它可能没有特殊情况就可以工作。 但是,这将更加安全,因为它不会复制RuntimeMethodHandle之类的东西。 如果您打算在核心运行时中将其他内容(例如System.Type)包含在层次结构中,则建议您以类似的方式显式地处理它们。
最后一种情况,也是最常见的,是简单地使用大致是由BinaryFormatter的使用相同的程序。 这些使我们能够从原始对象中弹出所有实例字段(公共或私有),进行克隆,然后将其粘贴到空对象中。 这里的好处是,GetUninitializedObject返回一个没有在其上运行ctor的新实例,这可能会导致问题并降低性能。
上述方法是否有效将在很大程度上取决于您的特定对象图及其中的数据。 如果您控制图中的对象,并且知道它们没有引用诸如Thread之类的愚蠢事物,那么上面的代码应该可以很好地工作。
测试:
这是我最初测试时写的内容:
class Test
{
public Test(string name, params Test[] children)
{
Print = (Action<StringBuilder>)Delegate.Combine(
new Action<StringBuilder>(delegate(StringBuilder sb) { sb.AppendLine(this.Name); }),
new Action<StringBuilder>(delegate(StringBuilder sb) { sb.AppendLine(this.Name); })
);
Name = name;
Children = children;
}
public string Name;
public Test[] Children;
public Action<StringBuilder> Print;
}
static void Main(string[] args)
{
Dictionary<string, Test> data2, data = new Dictionary<string, Test>(StringComparer.OrdinalIgnoreCase);
Test a, b, c;
data.Add("a", a = new Test("a", new Test("a.a")));
a.Children[0].Children = new Test[] { a };
data.Add("b", b = new Test("b", a));
data.Add("c", c = new Test("c"));
data2 = Clone(data);
Assert.IsFalse(Object.ReferenceEquals(data, data2));
//basic contents test & comparer
Assert.IsTrue(data2.ContainsKey("a"));
Assert.IsTrue(data2.ContainsKey("A"));
Assert.IsTrue(data2.ContainsKey("B"));
//nodes are different between data and data2
Assert.IsFalse(Object.ReferenceEquals(data["a"], data2["a"]));
Assert.IsFalse(Object.ReferenceEquals(data["a"].Children[0], data2["a"].Children[0]));
Assert.IsFalse(Object.ReferenceEquals(data["B"], data2["B"]));
Assert.IsFalse(Object.ReferenceEquals(data["B"].Children[0], data2["B"].Children[0]));
Assert.IsFalse(Object.ReferenceEquals(data["B"].Children[0], data2["A"]));
//graph intra-references still in tact?
Assert.IsTrue(Object.ReferenceEquals(data["B"].Children[0], data["A"]));
Assert.IsTrue(Object.ReferenceEquals(data2["B"].Children[0], data2["A"]));
Assert.IsTrue(Object.ReferenceEquals(data["A"].Children[0].Children[0], data["A"]));
Assert.IsTrue(Object.ReferenceEquals(data2["A"].Children[0].Children[0], data2["A"]));
data2["A"].Name = "anew";
StringBuilder sb = new StringBuilder();
data2["A"].Print(sb);
Assert.AreEqual("anew\r\nanew\r\n", sb.ToString());
}
最后说明:
老实说,当时这很有趣。 深入克隆数据模型通常是一件好事。 当今的现实是,大多数数据模型都是通过生成的深层克隆例程生成的,从而淘汰了上述黑客的用途。 我强烈建议您生成数据模型,并具有执行深度克隆的能力,而不是使用上面的代码。
最好的方法是序列化对象,然后返回反序列化的副本。 除了标记为不可序列化的对象之外,它将拾取对象的所有内容,并使继承序列化变得容易。
[Serializable]
public class ParentObj: ICloneable
{
private int myA;
[NonSerialized]
private object somethingInternal;
public virtual object Clone()
{
MemoryStream ms = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, this);
object clone = formatter.Deserialize(ms);
return clone;
}
}
[Serializable]
public class ChildObj: ParentObj
{
private int myB;
// No need to override clone, as it will still serialize the current object, including the new myB field
}
这不是最有表现力的事情,但替代方案也不是:反思。 此选项的好处是它可以无缝继承。
我认为您在这里无法正确实现ICloneable; 它需要没有参数的Clone()方法。 我推荐的是这样的:
public class ParentObj : ICloneable
{
public virtual Object Clone()
{
var obj = new ParentObj();
CopyObject(this, obj);
}
protected virtual CopyObject(ParentObj source, ParentObj dest)
{
dest.myA = source.myA;
}
}
public class ChildObj : ParentObj
{
public override Object Clone()
{
var obj = new ChildObj();
CopyObject(this, obj);
}
public override CopyObject(ChildObj source, ParentObj dest)
{
base.CopyObject(source, dest)
dest.myB = source.myB;
}
}
请注意,CopyObject()基本上是Object.MemberwiseClone(),想必您将做的不仅仅是复制值,还可以克隆作为类的任何成员。
尝试使用以下[使用关键字“ new”]
public class Parent
{
private int _X;
public int X{ set{_X=value;} get{return _X;}}
public Parent copy()
{
return new Parent{X=this.X};
}
}
public class Child:Parent
{
private int _Y;
public int Y{ set{_Y=value;} get{return _Y;}}
public new Child copy()
{
return new Child{X=this.X,Y=this.Y};
}
}
您应该改用MemberwiseClone
方法:
public class ParentObj : ICloneable
{
protected int myA;
public virtual Object Clone()
{
ParentObj newObj = this.MemberwiseClone() as ParentObj;
newObj.myA = this.MyA; // not required, as value type (int) is automatically already duplicated.
return newObj;
}
}
public class ChildObj : ParentObj
{
protected int myB;
public override Object Clone()
{
ChildObj newObj = base.Clone() as ChildObj;
newObj.myB = this.MyB; // not required, as value type (int) is automatically already duplicated
return newObj;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.