我应该实施ICloneable吗?

时间:2013-06-05 07:22:00

标签: c# generics copy-constructor icloneable

我写了一些代码,发现两个类(即下面的Fish和Mammal)有相同的模式,所以我决定用泛型来总结。

问题是,我需要从 base 类部分复制一个构造函数。

此外,使用new()约束(CS0304)无法修复此问题,因为构造函数不是默认值(0参数)。

我写这篇是因为我被告知实施ICloneable不是一个好习惯。

public class OneHeart {...}
public class TwoHeart {...}

public class Animal<TyHeart> /* : ICloneable ?*/ {
    public Animal(TyHeart heart) {
        if(heart==null) throw new ArgumentNullException();
        Heart = heart;
    }
    public Heart { get; set; }
}
public class Fish : Animal<OneHeart> {
    public Fish(OneHeart heart) : base(heart) {}
    public Fish(Fish fish) : base(fish.Heart) {} // copy ctor but no use?
}
public class Mammal : Animal<TwoHeart> {
    public Mammal(TwoHeart heart, Organ lung) : base(heart) {Lungs=lung;}
    public Mammal(Mammal mammal) : base(mammal.Heart) {Lungs=mammal.Lung;}
    public Organ Lungs {get; set;} // Mammal-only member:)
}
// The Zoo collects animals of only one type:
public class Zoo<TyHeart, TyAnimal> : LinkedList<TyAnimal> 
    where TyAnimal : Animal<TyHeart> {
    public Zoo() : base() {}
    public Zoo(Zoo<TyHeart, TyAnimal> srcZoo) {
        foreach(var animal in srcZoo) {
            // CS0304 compile error:
            base.AddLast(new TyAnimal(animal));
        }
    }
    ...
}

Fish and Mammal是源自Animal和

的唯一类

我知道他们都实现了复制构造函数。

编辑:不需要深层复制。

(类型)心脏和肺是单身并且在动物/鱼/哺乳动物之间共享。

2 个答案:

答案 0 :(得分:1)

实现ICloneable的一个问题是可变引用类型的存储位置(字段,变量,数组槽等)可用于封装标识,可变状态,两者或两者,但都不是.NET或其任何语言都包含任何标准约定,以指示上述哪个存储位置应该封装。

如果字段Foo1封装了身份但不包含可变状态,则George.Clone().Foo1应引用与George.Foo1相同的实例。

如果字段Foo2封装了可变状态但不包含身份,那么George.Clone().Foo2应该引用一个新对象,该对象被初始化为与George.Foo2具有相同的状态。

如果字段Foo3既不包含可变状态也不包含标识[即只有{identity}之外的George.Clone().Foo3可以引用George.Foo3,具有相同状态的新对象,或者具有相同状态的任何其他方便对象。

如果字段Foo4封装了可变状态和标识,则无法使用Clone方法有意义地克隆该类型的实例。

如果存在封装身份,可变状态,两者或两者的事物的不同存储位置类型,则由于任何引用类型字段或由类型封装的其他存储位置,因此几乎没有深层次与浅层区别。应该通过递归地应用上述规则克隆(请注意,对于正确构造的对象,这不会导致无限递归,因为两个对象无法有意义地封装彼此的可变状态,而其中至少有一个也封装了另一个的身份)。不幸的是,因为在类型系统中不存在这样的区别,除了实现ad-hoc克隆方法之外没有实际的解决方案,或者实现使用属性来决定它应该做什么的克隆方法,并使用硬编码行为来构建-in .NET类型,不包含任何特殊指标。

答案 1 :(得分:0)

如果没有通过在导出的构造函数声明中给出:base(...)来区别对,则无论如何都会调用默认的基础构造函数。

您不必实施ICloneable,因为它可能会让您在问题很深或者是浅拷贝方面遇到麻烦。但无论如何,您可以遵循克隆模式(仅以不同方式调用它)。

这是一个可以引导您实现具有或不具有ICloneable接口的克隆工具的资源:How to Implement ICloneable Interface in Derivable Classes in .NET

我们的想法是创建一个受保护的复制构造函数,而不是默认的构造函数。这样可以保留new()约束,并且仍然为派生类提供了在需要时克隆基础和派生内容的选项。