ICloneable<T>
泛型不存在是否有特殊原因?
如果我不需要每次克隆某些东西时都投射它,那会舒服得多。
除了安德烈的回复(我同意,+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>
? 你很快就开始实现许多相同的接口......与演员表相比......真的那么糟糕吗?
ICloneable 现在被认为是一个糟糕的 API,因为它没有指定结果是深拷贝还是浅拷贝。我认为这就是他们不改进这个界面的原因。
您可能可以使用类型化克隆扩展方法,但我认为它需要一个不同的名称,因为扩展方法的优先级低于原始方法。
我需要问,除了实现它之外,你会对接口做什么?接口通常仅在您转换为它时才有用(即此类是否支持'IBar'),或者具有接受它的参数或设置器(即我采用'IBar')。使用 ICloneable - 我们浏览了整个框架,但未能在任何地方找到一个单独的用法,而不是它的实现。我们也未能在“现实世界”中找到任何除了实现它之外的用途(在我们可以访问的约 60,000 个应用程序中)。
现在,如果您只想强制执行您希望您的“可克隆”对象实现的模式,那么这是一个完全好的用法 - 并继续。您还可以确定“克隆”对您的确切含义(即深或浅)。但是,在这种情况下,我们(BCL)不需要定义它。当需要在不相关的库之间交换类型为抽象的实例时,我们只在 BCL 中定义抽象。
大卫·基恩(BCL 团队)
我认为“为什么”这个问题是不必要的。有很多接口/类/等...非常有用,但不是 .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();
}
如果需要,自己编写接口非常容易:
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
最近阅读了文章为什么复制对象是一件可怕的事情?,我认为这个问题需要额外的澄清。这里的其他答案提供了很好的建议,但答案仍然不完整 - 为什么不ICloneable<T>
?
用法
所以,你有一个实现它的类。虽然以前您有一个想要的方法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>
。选择第二个,你会破坏接口本身,使应该做同样事情的方法返回不同的对象。
观点
您想要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
。
虽然这个问题很老了(写这个答案5年了:)并且已经回答了,但我发现这篇文章很好地回答了这个问题,在这里查看
编辑:
这是回答问题的文章的引述(请务必阅读完整的文章,其中包括其他有趣的内容):
Internet 上有许多参考资料指向 Brad Abrams 2003 年的一篇博客文章——当时他在微软工作——其中讨论了关于 ICloneable 的一些想法。可以在以下地址找到博客条目:Implementing ICloneable。尽管标题具有误导性,但这篇博文呼吁不要实现 ICloneable,主要是因为浅/深的混淆。文章以直截了当的建议结束:如果您需要克隆机制,请定义自己的克隆或复制方法,并确保您清楚地记录它是深拷贝还是浅拷贝。一个合适的模式是:
public <type> Copy();
一个大问题是他们不能将 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
这是一个很好的问题......不过,你可以自己做:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Andrey 说它被认为是一个糟糕的 API,但我还没有听说过这个接口被弃用的任何消息。这会破坏大量的接口...... Clone 方法应该执行浅拷贝。如果对象还提供深拷贝,则可以使用重载的克隆 (bool deep)。
编辑:我用于“克隆”对象的模式是在构造函数中传递一个原型。
class C
{
public C ( C prototype )
{
...
}
}
这消除了任何潜在的冗余代码实现情况。顺便说一句,谈到 ICloneable 的局限性,是否应该由对象本身决定是否应该执行浅克隆或深克隆,甚至是部分浅/部分深克隆?只要对象按预期工作,我们真的应该关心吗?在某些情况下,一个好的克隆实现很可能同时包含浅层和深层克隆。