241

ICloneable<T>泛型不存在是否有特殊原因?

如果我不需要每次克隆某些东西时都投射它,那会舒服得多。

4

9 回答 9

153

除了安德烈的回复(我同意,+1)-完成后,您ICloneable 可以选择显式实现以使公共Clone()返回一个类型化的对象:

public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}

当然,泛型还有第二个问题ICloneable<T>——继承。

如果我有:

public class Foo {}
public class Bar : Foo {}

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

于 2009-02-11T11:19:21.773 回答
120

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

您可能可以使用类型化克隆扩展方法,但我认为它需要一个不同的名称,因为扩展方法的优先级低于原始方法。

于 2009-02-11T11:16:44.760 回答
19

我需要问,除了实现它之外,你会对接口做什么?接口通常仅在您转换为它时才有用(即此类是否支持'IBar'),或者具有接受它的参数或设置器(即我采用'IBar')。使用 ICloneable - 我们浏览了整个框架,但未能在任何地方找到一个单独的用法,而不是它的实现。我们也未能在“现实世界”中找到任何除了实现它之外的用途(在我们可以访问的约 60,000 个应用程序中)。

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

大卫·基恩(BCL 团队)

于 2012-02-25T02:03:46.607 回答
14

我认为“为什么”这个问题是不必要的。有很多接口/类/等...非常有用,但不是 .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 (clone is null ) {
            throw new NullReferenceException( "Clone was not created." );
        }

        this.FillClone(clone);
        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();
}
于 2009-02-11T11:26:27.733 回答
10

如果需要,自己编写接口非常容易:

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}
于 2009-02-11T11:19:17.407 回答
9

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

  1. 用法

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

    然后,你可以有一个方法来检查一个 object is ICloneable。现在怎么办?您不能这样做is ICloneable<>,并且由于您不知道编译类型的对象类型,因此您不能使该方法成为通用方法。第一个真正的问题。

    所以你需要同时拥有ICloneable<T>and ICloneable,前者实现后者。因此,实现者需要实现这两种方法 -object Clone()T Clone(). 不,谢谢,我们已经玩得很开心了IEnumerable

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

  2. 观点

    您想要ICloneable<T>舒适,但舒适不是接口设计的目的,它们的含义是(通常是OOP)统一对象的行为(尽管在C#中,它仅限于统一外部行为,例如方法和属性,而不是他们的工作)。

    如果第一个原因还没有说服您,您可以反对也ICloneable<T>可以限制性地工作,以限制从 clone 方法返回的类型。但是,讨厌的程序员可以实现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();
    }
}

该解决方案为用户提供了舒适和预期的行为,但实施起来也太长了。如果我们不想让“舒适”方法返回当前类型,那么使用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;
}

它被命名为Copy以不与当前的Clone方法发生冲突(编译器更喜欢类型自己声明的方法而不是扩展方法)。限制是为了class速度(不需要空检查等)。

我希望这能澄清为什么不做的原因ICloneable<T>。但是,建议根本不要实施ICloneable

于 2014-11-12T19:29:15.470 回答
2

虽然这个问题很老了(写这个答案5年了:)并且已经回答了,但我发现这篇文章很好地回答了这个问题,在这里查看

编辑:

这是回答问题的文章的引述(请务必阅读完整的文章,其中包括其他有趣的内容):

Internet 上有许多参考资料指向 Brad Abrams 2003 年的一篇博客文章——当时他在微软工作——其中讨论了关于 ICloneable 的一些想法。可以在以下地址找到博客条目:Implementing ICloneable。尽管标题具有误导性,但这篇博文呼吁不要实现 ICloneable,主要是因为浅/深的混淆。文章以直截了当的建议结束:如果您需要克隆机制,请定义自己的克隆或复制方法,并确保您清楚地记录它是深拷贝还是浅拷贝。一个合适的模式是:public <type> Copy();

于 2014-10-29T05:38:20.917 回答
1

一个大问题是他们不能将 T 限制为同一类。例如,什么会阻止你这样做:

interface IClonable<T>
{
    T Clone();
}

class Dog : IClonable<JackRabbit>
{
    //not what you would expect, but possible
    JackRabbit Clone()
    {
        return new JackRabbit();
    }

}

他们需要一个参数限制,例如:

interfact IClonable<T> where T : implementing_type
于 2012-07-19T22:04:17.357 回答
0

这是一个很好的问题......不过,你可以自己做:

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

Andrey 说它被认为是一个糟糕的 API,但我还没有听说过这个接口被弃用的任何消息。这会破坏大量的接口...... Clone 方法应该执行浅拷贝。如果对象还提供深拷贝,则可以使用重载的克隆 (bool deep)。

编辑:我用于“克隆”对象的模式是在构造函数中传递一个原型。

class C
{
  public C ( C prototype )
  {
    ...
  }
}

这消除了任何潜在的冗余代码实现情况。顺便说一句,谈到 ICloneable 的局限性,是否应该由对象本身决定是否应该执行浅克隆或深克隆,甚至是部分浅/部分深克隆?只要对象按预期工作,我们真的应该关心吗?在某些情况下,一个好的克隆实现很可能同时包含浅层和深层克隆。

于 2009-02-11T11:19:40.137 回答