是否存在ICloneable<T>
不存在泛型的特定原因?
如果我不需要每次克隆某些东西时都将其强制转换,它将更加舒适。
是否存在ICloneable<T>
不存在泛型的特定原因?
如果我不需要每次克隆某些东西时都将其强制转换,它将更加舒适。
Answers:
ICloneable现在被认为是错误的API,因为它没有指定结果是深拷贝还是浅拷贝。我认为这就是为什么他们不改进此界面的原因。
您可能可以执行类型化克隆扩展方法,但我认为它将需要使用其他名称,因为扩展方法的优先级低于原始方法。
List<T>
使用克隆方法,我希望它会产生一个List<T>
与原始列表中的项目具有相同标识的,但是我希望可以根据需要复制任何内部数据结构,以确保对一个列表所做的任何操作都不会影响存储在另一个中的项目的标识。歧义在哪里?克隆的一个更大的问题是“钻石问题”的一种变体:如果CloneableFoo
继承自[not publicly可克隆] Foo
,则应CloneableDerivedFoo
继承自...
identity
列表本身(例如列表的情况)?但是,忽略这一点,您的期望并不是人们在调用或实现时可能唯一的想法Clone
。如果实现其他列表的图书馆作者没有遵循您的期望怎么办?该API应该是毫不含糊的,而不是毫无疑问的。
Clone
其所有部分的类,则该类将无法正常工作-取决于该部分是由您还是由那个人实现的喜欢深克隆的人。您关于模式的观点是有效的,但是在API中使用IMHO还不够清楚-应该调用它ShallowCopy
来强调这一点,或者根本不提供它。
除了安德烈的答复(我同意,+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>
?您很快就开始实现许多相同的接口...与强制转换相比...真的那么糟糕吗?
我要问的是,除了实现该接口之外,您还要对接口做什么?接口通常仅在您强制转换时才有用(即此类是否支持“ IBar”),或者具有接受该参数的参数或设置器(即我采用“ IBar”)。使用ICloneable-我们遍历了整个框架,但未能在除实现之外的任何地方找到一种用法。我们也没有找到“现实世界”中任何除了实现它之外还有其他用途的用法(在我们可以访问的约60,000个应用程序中)。
现在,如果您只想强制实施一种模式,以实现您的“可克隆”对象,那就完全可以了-继续。您还可以确定“克隆”到底对您意味着什么(即深浅)。但是,在那种情况下,我们(BCL)不需要定义它。我们仅在需要在不相关的库之间交换作为抽象类型输入的实例时,才在BCL中定义抽象。
大卫·基恩(BCL团队)
ICloneable<out T>
如果ISelf<out T>
使用单个Self
type 方法从它继承而来,则可能会非常有用T
。一个人通常不需要“可克隆的东西”,但是很可能需要一个T
可克隆的东西。如果实现了可克隆对象ISelf<itsOwnType>
,则需要克隆的a例程T
可以接受type的参数ICloneable<T>
,即使并非所有可克隆派生类T
都具有相同的祖先。
ICloneable<T>
可能是有用的,尽管维护并行可变和不变类的更广泛的框架可能会更有用。换句话说,需要查看某种类型的Foo
包含但既不打算对其进行突变也不希望它永远不会改变的代码可以使用IReadableFoo
,而...
Foo
可以使用ImmutableFoo
while代码,要操纵它的可以使用MutableFoo
。给出任何类型的代码都IReadableFoo
应该能够获得可变或不可变的版本。这样的框架会很不错,但是不幸的是,我找不到以通用方式进行设置的任何不错的方法。如果有一种一致的方法可以为类创建只读包装,则可以将此类内容与用于组合ICloneable<T>
保存T
' 的类的不可变副本一起使用。
List<T>
,以使克隆的对象List<T>
是一个新集合,其中包含指向原始集合中所有相同对象的指针,则有两种简单的方法而无需使用ICloneable<T>
。第一个是Enumerable.ToList()
扩展方法:List<foo> clone = original.ToList();
第二个是采用的List<T>
构造函数IEnumerable<T>
:List<foo> clone = new List<foo>(original);
我怀疑扩展方法可能只是在调用构造函数,但是这两种方法都能满足您的要求。;)
我认为“为什么”这个问题是不必要的。有很多接口/类/等等...这是非常有用的,但不是.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(); }
}
如果需要,可以自己编写接口,这很容易:
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
最近阅读过文章为什么复制对象是一件可怕的事情吗?,我认为这个问题需要进一步说明。这里的其他答案提供了很好的建议,但答案仍然不完整-为什么不ICloneable<T>
呢?
用法
因此,您有一个实现它的类。以前您有想要的方法ICloneable
,但现在必须通用才能接受ICloneable<T>
。您需要对其进行编辑。
然后,您可能拥有一种检查object是否存在的方法is ICloneable
。现在怎么办?您不能这样做,is ICloneable<>
而且由于您不知道对象的类型为compile-type,因此无法使该方法通用。第一个真正的问题。
因此,您需要同时具有ICloneable<T>
和和ICloneable
,前者要实现后者。因此,实现者将需要同时实现方法- object Clone()
和T Clone()
。不,谢谢,我们已经有足够的乐趣了IEnumerable
。
如前所述,继承也很复杂。虽然协方差似乎可以解决此问题,但派生类型需要实现ICloneable<T>
自己的类型,但是已经有一个具有相同签名(基本上是=参数)的方法- Clone()
基类。使新的克隆方法接口显式化是没有意义的,您将失去创建时寻求的优势ICloneable<T>
。因此,添加new
关键字。但不要忘记,您还需要重写基类Clone()
(实现必须对所有派生类保持统一,即从每个克隆方法返回相同的对象,因此基克隆方法必须是virtual
!)!但不幸的是,你不能同时override
和new
具有相同签名的方法。选择第一个关键字,添加时您将失去想要达到的目标ICloneable<T>
。选择第二个,您将破坏接口本身,使应该执行相同操作的方法返回不同的对象。
点
您想要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();
}
}
该解决方案为用户提供了舒适感和预期的行为,但是实施起来也太长了。如果我们不想让“ comfortable”方法返回当前类型,则拥有just会容易得多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
。
ICloneable
是用于值类型,在这种情况下它可能会绕过Clone方法的装箱,这意味着您没有装箱值。并且由于可以自动(浅)克隆结构,因此无需实现它(除非您明确指出它意味着深层复制)。
尽管这个问题很老(距撰写此答案5年之久),并且已经被回答了,但是我发现本文对这个问题的回答很好,请在此处查看
编辑:
这是回答问题的文章引文(请务必阅读全文,其中包括其他有趣的内容):
Internet上有许多参考文献,它们指向Brad Abrams于2003年在Microsoft任职时发表的一篇博客文章,其中讨论了有关ICloneable的一些想法。博客条目可在以下地址找到:实现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
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
可以约束T
匹配其自身的类型,也不会强制实现Clone()
返回与被克隆对象相似的任何远程对象。此外,我建议如果使用接口协方差,则最好是将实现的类ICloneable
密封,使接口ICloneable<out T>
包含Self
可以返回自身的属性,以及……
ICloneable<BaseType>
或施加或约束ICloneable<ICloneable<BaseType>>
。所BaseType
讨论的对象应具有protected
用于克隆的方法,该方法将由实现的类型调用ICloneable
。这种设计将允许以下可能性:a Container
,a CloneableContainer
,a FancyContainer
和a CloneableFancyContainer
,后者可在需要可克隆派生的Container
或需要a的代码中使用FancyContainer
(但不在乎是否可克隆)。
FancyList
类型可能会被合理地克隆,但是派生类可能会自动将其状态保存在磁盘文件中(在构造函数中指定)。无法克隆派生类型,因为其状态将附加到可变单例(文件)的状态上,但这不排除在需要a的大多数功能FancyList
但不需要的地方使用派生类型克隆它。
这是一个很好的问题……尽管您可以自己做:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
安德烈(Andrey)说这被认为是一个不好的API,但我还没有听说过该接口已被弃用。这会破坏大量的接口。Clone方法应该执行浅表复制。如果对象还提供了深层复制,则可以使用重载的Clone(bool deep)。
编辑:我用于“克隆”对象的模式,正在构造函数中传递原型。
class C
{
public C ( C prototype )
{
...
}
}
这消除了任何潜在的冗余代码实现情况。BTW在谈到ICloneable的局限性时,不是由对象本身来决定是否应该执行浅克隆或深克隆,甚至是部分浅/部分深克隆?只要对象按预期工作,我们真的应该关心吗?在某些情况下,良好的Clone实现可能很好地包括浅层克隆和深层克隆。