为什么没有ICloneable <T>?

Blu*_*nce 210 .net c# icloneable

是否存在通用ICloneable<T>不存在的特殊原因?

如果我不想在每次克隆东西时都需要施放它,那会更舒服.

Mar*_*ell 136

除了安德烈的答复(我同意,+1) -当ICloneable 做了,你也可以选择明确的实施,使公众Clone()返回一个类型的对象:

public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
Run Code Online (Sandbox Code Playgroud)

当然,第二个问题是泛型ICloneable<T>继承.

如果我有:

public class Foo {}
public class Bar : Foo {}
Run Code Online (Sandbox Code Playgroud)

我实施了ICloneable<T>,然后实施了ICloneable<Foo>吗?ICloneable<Bar>?你很快就开始实现了很多相同的接口......与演员相比......真的很糟糕吗?

  • +1我一直认为协方差问题才是真正的原因 (9认同)
  • 建议的解决方案起初看起来很棒...直到你意识到在类层次结构中,你希望'public Foo Clone()'在派生类中被虚拟和覆盖,这样它们就可以返回它们自己的类型(并克隆它们自己的字段课程).遗憾的是,您无法更改覆盖中的返回类型(提到的协方差问题).所以,你再次完全回到原来的问题 - 显式实现并没有真正为你带来太多的东西(除了返回你的层次结构的基本类型而不是'对象'),而你又回到了大部分的克隆()调用结果.呸! (7认同)
  • 我的错误:事实证明,尽管被定义为私有,但是_will_被调用的方法应该被强制转换为IClonable(CLR通过C#2nd Ed,p.319).看起来像一个奇怪的设计决定,但它确实存在.所以Foo.Clone()给出了第一种方法,并且((ICloneable)Foo).Clone()给出了第二种方法. (3认同)

And*_*kin 107

ICloneable现在被认为是一个糟糕的API,因为它没有指定结果是深拷贝还是浅拷贝.我认为这就是为什么他们不改进这个界面.

您可以执行类型化克隆扩展方法,但我认为它需要不同的名称,因为扩展方法的优先级低于原始方法.

  • 我不同意,不好或不坏,它对于SHALLOW克隆非常有用,并且在那些情况下确实需要克隆<T>,以节省不必要的装箱和拆箱. (8认同)
  • @supercat你没有考虑到它是.NET框架的一部分,而不是你的应用程序的一部分.因此,如果你创建一个不进行深度克隆的类,那么其他人会创建一个深度克隆的类并在其所有部分调用"Clone",它将无法预测 - 取决于该部分是否由您实现或者那个喜欢深度克隆的人.关于模式的观点是有效的,但是在API中使用恕我直言还不够清楚 - 它应该被称为"ShallowCopy"来强调这一点,或者根本没有提供. (5认同)
  • @supercat:我不能说我完全理解你的期望,因为你会考虑列表本身的“身份”,例如(在列表列表的情况下)?然而,忽略这一点,您的期望并不是人们在调用或实现 `Clone` 时可能拥有的唯一可能想法。如果实现一些其他列表的库作者不符合您的期望怎么办?API 应该是非常明确的,而不是毫无争议的明确。 (4认同)
  • @AndreyShchekin:关于深度克隆和浅层克隆有什么不清楚?如果`List <T>`有克隆方法,我希望它产生一个`List <T>`,其项目与原始列表中的项目具有相同的身份,但我希望任何内部数据结构都是重复的根据需要确保对一个列表执行的任何操作都不会影响另一个列表中存储的项目的*身份*.歧义在哪里?克隆的一个更大的问题是"钻石问题"的变化:如果`CloneableFoo`继承自[not public cloneable]`Foo`,那么`CloneableDerivedFoo`应该来自...... (2认同)
  • @supercat所以你在谈论浅拷贝.但是,深层复制没有任何根本性的错误,例如,如果要对内存中的对象执行可逆事务.您的期望是基于您的用例,其他人可能有不同的期望.如果他们将对象放入对象(反之亦然),结果将是意外的. (2认同)

Dav*_*ean 19

我需要问一下,除了实现它之外,你究竟会对接口做些什么呢?接口通常仅在您转换为它时才有用(即此类支持'IBar'),或者具有接受它的参数或设置器(即我采用'IBar').使用ICloneable - 我们经历了整个框架,并且未能在任何地方找到一个不同于其实现的单一用法.我们也未能在"现实世界"中找到任何除了实现它之外的其他用法(在我们有权访问的~60,000个应用程序中).

现在,如果您只想强制执行一个您希望"可复制"对象实现的模式,那么这是一个非常好的用法 - 并继续.您还可以确定"克隆"对您意味着什么(即深或浅).但是,在这种情况下,我们(BCL)不需要定义它.当需要交换类型为不相关库之间的抽象的实例时,我们只在BCL中定义抽象.

大卫基恩(BCL队)

  • 非泛型 ICloneable 不是很有用,但如果它继承自“ISelf&lt;out T&gt;”,并且具有类型为“T”的单一方法“Self”,那么“ICloneable&lt;out T&gt;”可能会非常有用。人们并不经常需要“可克隆的东西”,但人们很可能需要一个可克隆的“T”。如果可克隆对象实现了“ISelf&lt;itsOwnType&gt;”,则需要可克隆“T”的例程可以接受“ICloneable&lt;T&gt;”类型的参数,即使并非“T”的所有可克隆派生都共享一个公共值祖先。 (2认同)

TcK*_*cKs 12

我认为"为什么"这个问题是不必要的.有很多接口/类/等...这非常有用,但不是.NET Frameworku基础库的一部分.

但是,主要是你可以自己做.

public interface ICloneable<T> : ICloneable {
    new T Clone();
}

public abstract class CloneableBase<T> : ICloneable<T> where T : CloneableBase<T> {
    public abstract T Clone();
    object ICloneable.Clone() { return this.Clone(); }
}

public abstract class CloneableExBase<T> : CloneableBase<T> where T : CloneableExBase<T> {
    protected abstract T CreateClone();
    protected abstract void FillClone( T clone );
    public override T Clone() {
        T clone = this.CreateClone();
        if ( object.ReferenceEquals( clone, null ) ) { throw new NullReferenceException( "Clone was not created." ); }
        return clone
    }
}

public abstract class PersonBase<T> : CloneableExBase<T> where T : PersonBase<T> {
    public string Name { get; set; }

    protected override void FillClone( T clone ) {
        clone.Name = this.Name;
    }
}

public sealed class Person : PersonBase<Person> {
    protected override Person CreateClone() { return new Person(); }
}

public abstract class EmployeeBase<T> : PersonBase<T> where T : EmployeeBase<T> {
    public string Department { get; set; }

    protected override void FillClone( T clone ) {
        base.FillClone( clone );
        clone.Department = this.Department;
    }
}

public sealed class Employee : EmployeeBase<Employee> {
    protected override Employee CreateClone() { return new Employee(); }
}
Run Code Online (Sandbox Code Playgroud)

  • @Krythic ie:高级数据结构,如 b 树、红黑树、循环缓冲区。在 09 年,没有元组、通用弱引用、并发集合…… (3认同)

Mau*_*fer 10

这是很容易写自己的界面,如果你需要它:

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}
Run Code Online (Sandbox Code Playgroud)

  • 那个界面应该如何工作呢?对于可以转换为类型T的克隆操作的结果,克隆的对象必须从T派生.没有办法设置这样的类型约束.实际上,我唯一可以看到iCloneable返回结果的是iCloneable类型. (2认同)
  • @Mauricio Scheffer:我知道发生了什么.没有类型实际实现iCloneable(T).相反,每种类型都实现了iCloneable(自身).我想,这会起作用,尽管它所做的只是移动一个类型.太糟糕了,没有办法将字段声明为具有继承约束和接口; 让克隆方法返回一个半可克隆的对象(克隆暴露为仅受保护的方法)基类型,但具有可克隆类型约束将是有帮助的.这将允许对象接受碱基类型的半可克隆衍生物的可克隆衍生物. (2认同)

Ill*_*ack 8

最近阅读了文章为什么要复制一个对象是一件可怕的事情?,我认为这个问题需要额外的肯定.这里的其他答案提供了很好的建议,但答案仍然不完整 - 为什么不ICloneable<T>呢?

  1. 用法

    所以,你有一个实现它的类.虽然以前你有一个想要的方法ICloneable,但它现在必须是通用的才能接受ICloneable<T>.你需要编辑它.

    然后,你可能有一个方法来检查一个对象is ICloneable.现在怎么办?你不能这样做is ICloneable<>,因为你不知道在compile-type中对象的类型,你不能使该方法通用.第一个真实问题.

    所以,你需要同时拥有ICloneable<T>ICloneable,前者实现了后者.因此,实施者需要实现这两种方法 - object Clone()T Clone().不,谢谢,我们已经有了足够的乐趣IEnumerable.

    正如已经指出的那样,继承的复杂性也很大.虽然协方差似乎可以解决这个问题,但派生类型需要实现ICloneable<T>自己的类型,但是已经有一个方法具有相同的签名(基本上是=参数) - Clone()基类的方法.使您的新克隆方法界面显式无意义,您将失去创建时所寻求的优势ICloneable<T>.所以添加new关键字.但是不要忘记你还需要覆盖基类' Clone()(对于所有派生类,实现必须保持一致,即从每个克隆方法返回相同的对象,因此必须使用基本克隆方法virtual)!但是,遗憾的是,你不能同时使用相同的签名overridenew方法.选择第一个关键字,您将失去添加时想要的目标ICloneable<T>.选择第二个,你将破坏界面本身,使得应该做同样的方法返回不同的对象.

  2. 你想要ICloneable<T>舒适,但舒适不是界​​面的设计,它们的意义是(通常是OOP)来统一对象的行为(尽管在C#中,它仅限于统一外部行为,例如方法和属性,而不是他们的工作).

    如果第一个原因尚未说服您,那么您可以ICloneable<T>限制性地限制从克隆方法返回的类型.但是,讨厌的程序员可以实现ICloneable<T>T不是实现它的类型.因此,为了实现您的限制,您可以为泛型参数添加一个很好的约束:
    public interface ICloneable<T> : ICloneable where T : ICloneable<T>
    当然没有那个限制where,您仍然不能限制T是实现接口的类型(您可以从ICloneable<T>不同类型派生实现它).

    你看,即使这个目的也无法实现(原来ICloneable也失败了,没有接口可以真正限制实现类的行为).

正如您所看到的,这证明了通用接口既难以完全实现,也非常不需要和无用.

但回到这个问题,你真正寻求的是在克隆物体时获得安慰.有两种方法可以做到:

其他方法

public class Base : ICloneable
{
    public Base Clone()
    {
        return this.CloneImpl() as Base;
    }

    object ICloneable.Clone()
    {
        return this.CloneImpl();
    }

    protected virtual object CloneImpl()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public new Derived Clone()
    {
        return this.CloneImpl() as Derived;
    }

    protected override object CloneImpl()
    {
        return new Derived();
    }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案为用户提供了舒适性和预期的行为,但实施起来也太长了.如果我们不想让"舒适"方法返回当前类型,那么就更容易拥有public virtual object Clone().

那么让我们看看"终极"解决方案 - C#中真正意图给予我们安慰的是什么?

扩展方法!

public class Base : ICloneable
{
    public virtual object Clone()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public override object Clone()
    {
        return new Derived();
    }
}

public static T Copy<T>(this T obj) where T : class, ICloneable
{
    return obj.Clone() as T;
}
Run Code Online (Sandbox Code Playgroud)

它被命名为Copy,不与当前的Clone方法冲突(编译器更喜欢类型自己声明的方法而不是扩展方法).该class约束是有速度(不需要空检查等).

我希望这澄清了为什么不这样做的原因ICloneable<T>.但是,建议不要实施ICloneable.