ICloneable <T>がないのはなぜですか?


222

ジェネリックICloneable<T>が存在しない特別な理由はありますか?

クローンを作成するたびにキャストする必要がない場合は、はるかに快適です。


6
番号!「理由」に敬意を表して、私はあなたに同意します。彼らはそれを実装すべきだったのです!
Shimmy Weitzhandler、2010年

Microsoftが定義したことは素晴らしいことでした(brew-your-ownインターフェースの問題は、2つの異なるアセンブリのインターフェースが、たとえ意味的に同一であっても互換性がないことです)。私がインターフェイスを設計した場合、Clone、Self、CloneIfMutableの3つのメンバーがあり、すべてTを返します(最後のメンバーは、必要に応じてCloneまたはSelfを返します)。Selfメンバーは、型キャストを必要とせずに、ICloneable(of Foo)をパラメーターとして受け入れ、それをFooとして使用することを可能にします。
スーパーキャット2011年

これにより、継承可能なクラスが保護された「クローン」メソッドを公開し、パブリックなものを公開するシールされた派生物を持つ、適切なクローニングクラス階層が可能になります。たとえば、Pile、CloneablePile:Pile、EnhancedPile:Pile、およびCloneableEnhancedPile:EnhancedPileがあり、いずれも複製された場合(すべてがパブリッククローニングメソッドを公開しているわけではありませんが)壊れることはなく、さらに、EntireancePile:EnhancedPile(壊れる可能性があります)クローンを作成したが、クローン作成方法を公開していない場合)。(Pileの)ICloneableを受け入れるルーチンは、CloneablePileまたはCloneableEnhancedPile ...を受け入れることができます...
supercat

... CloneableEnhancedPileはCloneablePileを継承しませんが。EnhancedPileがCloneablePileから継承された場合、更なるEnhancedPileはパブリッククローンメソッドを公開する必要があり、それをクローンすることを期待するコードに渡され、リスコフの代替可能性の原則に違反する可能性があることに注意してください。CloneableEnhancedPileはICloneable(Of EnhancedPile)を実装し、ICloneable(Of Pile)を暗黙的に実装するため、Pileの複製可能な派生物を期待するルーチンに渡すことができます。
スーパーキャット2011年

回答:


111

ICloneableは、結果が深いコピーであるか浅いコピーであるかを指定しないため、現在は不正なAPIと見なされています。これが彼らがこのインターフェースを改善しない理由だと思います。

型指定された複製拡張メソッドを実行できるかもしれませんが、拡張メソッドは元のメソッドよりも優先度が低いため、別の名前が必要になると思います。


8
良くも悪くも、私は同意しません。SHALLOWのクローン作成に役立つことがあります。そのような場合は、不要なボックス化とボックス化解除を保存するためにClone <T>が本当に必要です。
Shimmy Weitzhandler、2010年

2
@AndreyShchekin:深いvs浅いクローニングについて不明確なことは何ですか?List<T>クローンメソッドがあった場合List<T>、元のリストと同じIDを持つアイテムが生成されることを期待しますが、1つのリストに対して何も行われないようにするために、内部データ構造が必要に応じて複製されることを期待します他に保存されているアイテムのID。あいまいさはどこにありますか?クローン作成のより大きな問題には、「ダイヤモンドの問題」のバリエーションが伴います。CloneableFoo[パブリッククローンではない]から継承する場合FooCloneableDerivedFoo派生元は...
supercat

1
@supercat:identityたとえばリスト(リストのリストの場合)自体について何を検討するのかなど、期待を完全に理解しているとは言えませんか?ただし、それを無視すると、を呼び出したり実装したりするときに考えられるのは、あなたの期待だけではありませんClone。他のリストを実装しているライブラリ作成者があなたの期待に沿わない場合はどうなりますか?APIは、明白であり、間違いなく明確でなければなりません。
Andrey Shchekin

2
@supercatそれであなたは浅いコピーについて話しています。ただし、ディープコピーに根本的な問題はありません。たとえば、メモリ内オブジェクトでリバーシブルトランザクションを実行する場合などです。あなたの期待はあなたのユースケースに基づいています、他の人々は異なる期待を持っているかもしれません。彼らがあなたのオブジェクトを彼らのオブジェクトに入れたら(またはその逆)、結果は予想外になります。
Andrey Shchekin

5
@supercatは考慮しません。これは.NETフレームワークの一部であり、アプリケーションの一部ではありません。したがって、ディープクローニングを行わないクラスを作成した場合、他の誰かがディープクローニングをClone行い、そのすべてのパーツを呼び出すクラスを作成すると、そのパーツが自分とその人物のどちらによって実装されたかに応じて、予測どおりに機能しません。ディープクローニングが好きな人。パターンに関するあなたのポイントは有効ですが、APIにIMHOがあることは十分に明確ではありませんShallowCopy。ポイントを強調するために呼び出す必要があるか、まったく提供されません。
Andrey Shchekin 2012

141

アンドレイの回答に加えて、(私は同意する、1) -ときICloneable されて行わ、あなたはまた、公開するための明示的な実装を選択することができClone()、戻り型指定されたオブジェクトを:

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

もちろん、ジェネリックには2番目の問題がありますICloneable<T>-継承です。

私が持っている場合:

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

そして私は実装しましたICloneable<T>、そして私は実装しICloneable<Foo>ますか?ICloneable<Bar>?すぐに多くの同一のインターフェースの実装を開始します...キャストと比較して...そしてそれは本当にとても悪いですか?


10
+1私はいつも共分散問題が本当の理由だと思っていました
Tamas Czinege 2009

これには問題があります。明示的なインターフェイスの実装はプライベートである必要があり、ある時点でFooをICloneableにキャストする必要がある場合に問題が発生します...ここに何か不足していますか?
ジョエル、

3
私の間違い:プライベートとして定義されているにもかかわらず、FooがIClonable(C#2nd Ed経由のCLR、p.319)にキャストされた場合、メソッド呼び出されることがわかりました。奇妙なデザイン決定のようですが、それはあります。したがって、Foo.Clone()は最初のメソッドを提供し、((ICloneable)Foo).Clone()は2番目のメソッドを提供します。
ジョエル、

実際、明示的なインターフェースの実装は、厳密に言えば、パブリックAPIの一部です。キャストを介してパブリックに呼び出すことができるという点で。
Marc Gravell

8
提案されたソリューションは最初は素晴らしいようです...クラス階層で、 'public Foo Clone()'を仮想化し、派生クラスでオーバーライドして、独自の型を返す(そして独自のフィールドを複製できる)ことを理解するまでコース)。悲しいことに、オーバーライドでは戻り値の型を変更できません(前述の共分散の問題)。したがって、再び元の問題に完全に戻ります-明示的な実装は実際にはあまり購入しません(「オブジェクト」ではなく階層の基本タイプを返すことを除いて)、そしてあなたはほとんどのキャストに戻ってきますClone()呼び出しの結果。おい!
ケン・ベケット

18

私は質問する必要があります。それを実装する以外に、インターフェイスを正確にどうしますか?インターフェイスは通常、キャストする(つまり、このクラスが 'IBar'をサポートする)か、それを受け取るパラメーターまたはセッター(つまり、 'IBar'を受け取る)がある場合にのみ役立ちます。ICloneableを使用した場合-フレームワーク全体を調べたところ、その実装以外の場所で単一の使用法を見つけることができませんでした。また、それを実装する以外の何かを実行する「現実の世界」での使用法を見つけることができませんでした(アクセスできる〜60,000のアプリで)。

ここで、「クローン可能な」オブジェクトに実装させたいパターンを適用したいだけであれば、それは完全に適切な使用法です。また、「クローニング」が自分にとって何を意味するか(つまり、深いか浅いか)を正確に決定することもできます。ただし、その場合、私たち(BCL)が定義する必要はありません。BCLで抽象化を定義するのは、関係のないライブラリ間でその抽象化として型指定されたインスタンスを交換する必要がある場合のみです。

デビッドキーン(BCLチーム)


1
非ジェネリックICloneableはあまり有用ではありませんが、ICloneable<out T>から継承しISelf<out T>Selfタイプが1つのメソッドである場合は非常に有用ですT。多くの場合、「複製可能なもの」は必要ありませんが、複製可能なものが必要な場合もありTます。複製可能なオブジェクトがを実装している場合、複製可能ISelf<itsOwnType>なを必要とするルーチンは、Tの複製可能なICloneable<T>派生物のすべてがT共通の祖先を共有していなくても、タイプのパラメータを受け入れることができます。
スーパーキャット2012年

ありがとう。これは、前述のキャストの状況に似ています(つまり、このクラスは 'IBar'をサポートしていますか)。残念なことに、Tがクローン可能であるという事実を実際に利用する非常に限定された孤立したシナリオしか思いつきませんでした。状況を考えていますか?
David Kean

.netで時々厄介なことの1つは、コレクションのコンテンツが値として表示されることになっているときに可変コレクションを管理することです(つまり、後でコレクションに含まれるアイテムのセットを後で知りたいと思います)。のようなものICloneable<T>が役に立つかもしれませんが、並列の可変クラスと不変クラスを維持するためのより広いフレームワークがより役立つかもしれません。言い換えれば、必要なコードはいくつかの種類がものを見るためにFoo含まれていますが、どちらもそれを変異させず、それは今までに変更が使用できないことを期待するつもりはありませんIReadableFoo...、一方で
supercat

...の内容を保持したいコードFooImmutableFoowhileを使用でき、それを操作したいコードはを使用できますMutableFoo。任意のタイプのコードIReadableFooは、可変バージョンまたは不変バージョンのいずれかを取得できる必要があります。そのようなフレームワークはいいですが、残念ながら、一般的な方法で設定するための良い方法を見つけることができません。クラスの読み取り専用ラッパーを作成する一貫した方法がある場合、そのようなものをと組み合わせて使用​​しICloneable<T>て、T' を保持するクラスの不変のコピーを作成できます。
スーパーキャット2012年

@supercatを複製したい場合List<T>、つまり、複製されたList<T>ものが、元のコレクション内の同じオブジェクトすべてへのポインタを保持する新しいコレクションである場合、なしでそれを行う簡単な方法が2つありますICloneable<T>。最初は、Enumerable.ToList()拡張メソッド:List<foo> clone = original.ToList();第二は、List<T>取るコンストラクタIEnumerable<T>List<foo> clone = new List<foo>(original);私は、拡張メソッドは、おそらくコンストラクタを呼び出している疑いがあるが、これらの両方は、あなたが要求しているものを行います。;)
CptRobby 2013年

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(); }
}

継承可能なクラスの実行可能な複製メソッドは、開始点としてObject.MemberwiseCloneを使用する(またはリフレクションを使用する)必要があります。そうしないと、複製が元のオブジェクトと同じ型になる保証はありません。あなたが興味を持っているなら、私はかなりいいパターンを持っています。
スーパーキャット

@supercatはなぜそれを答えにしないのですか?
nawfal 2013

「多くのインターフェース/クラス/などがあります...これは非常に便利ですが、.NET Frameworkuベースライブラリの一部ではありません。」例を挙げていただけますか?
Krythic 2016

1
@Krythic ie:bツリー、赤黒ツリー、サークルバッファーなどの高度なデータ構造。'09では、タプル、一般的な弱参照、同時コレクションはありませんでした...
TcKs '26

10

必要に応じて、自分でインターフェース作成するのは非常に簡単です。

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

著作権を尊重したため、リンクが壊れたため、スニペットを含めました...
Shimmy Weitzhandler

2
そして、そのインターフェースはどのように機能するはずですか?クローン操作の結果をタイプTにキャスト可能にするには、クローンされるオブジェクトをTから派生させる必要があります。そのようなタイプ制約を設定する方法はありません。現実的に言えば、iCloneableが返された結果を確認できるのは、タイプiCloneableだけです。
スーパーキャット

@supercat:はい、それはまさにClone()が返すものです:ICloneable <T>を実装するT。MbUnitはこのインターフェースを長年使用してきたので、はい、動作します。実装については、MbUnitのソースをのぞいてみてください。
Mauricio Scheffer、

2
@Mauricio Scheffer:何が起こっているのかわかります。実際にiCloneable(of T)を実装する型はありません。代わりに、各タイプは(それ自体の)iCloneableを実装します。それはうまくいくと思いますが、タイプキャストを移動するだけです。フィールドに継承制約とインターフェース制約があると宣言する方法がないのは残念です。複製メソッドがセミクローン可能(保護されたメソッドとしてのみ公開される複製)の基本タイプのオブジェクトを返すが、複製可能タイプの制約があると便利です。これにより、オブジェクトは、基本タイプのセミクローン可能な派生物のクローン可能な派生物を受け入れることができます。
スーパーキャット

@Mauricio Scheffer:ちなみに、(Tの)ICloneableも(T型の)読み取り専用のSelfプロパティをサポートすることをお勧めします。これにより、メソッドは(Fooの)ICloneableを受け入れ、それを(Selfプロパティを介して)Fooとして使用できます。
スーパーキャット

9

なぜオブジェクトをコピーするのが大変なことかという記事を最近読んだことがありますか?、この質問には追加の分類が必要だと思います。ここでの他の答えは良いアドバイスを提供しますが、それでも答えは完全ではありません-なぜ駄目なのICloneable<T>ですか?

  1. 使用法

    したがって、それを実装するクラスがあります。以前は必要なメソッドがありましたがICloneable、今では受け入れるためにジェネリックでなければなりませんICloneable<T>。編集する必要があります。

    次に、オブジェクトかどうかをチェックするメソッドを取得できますis ICloneable。今何?できません。is ICloneable<>コンパイルタイプではオブジェクトのタイプがわからないため、メソッドをジェネリックにすることはできません。最初の本当の問題。

    したがって、ICloneable<T>との両方が必要ですICloneable。前者は後者を実装します。したがって、実装者は- object Clone()との両方のメソッドを実装する必要がありT Clone()ます。いいえ、ありがとうございIEnumerableます。すでにで十分に楽しんでいます。

    すでに指摘したように、継承の複雑さもあります。共分散がこの問題を解決するように見えるかもしれませんが、派生型ICloneable<T>はそれ自体の型を実装する必要がありますが、基本Clone()クラスの-同じシグニチャー(=パラメーター、基本的に)を持つメソッドがすでにあります。新しいクローンメソッドインターフェースを明示的にしても意味がありませんICloneable<T>。作成時に求めていた利点が失われます。したがって、newキーワードを追加します。ただし、基本クラスもオーバーライドする必要があることを忘れないでくださいClone()(すべての派生クラスで実装を統一する必要があります。つまり、すべてのクローンメソッドから同じオブジェクトを返すため、基本クローンメソッドはでなければなりませんvirtual)。しかし、残念ながら、両方overridenew同じシグネチャを持つメソッド。最初のキーワードを選択すると、追加時に目標を失うことになりますICloneable<T>。2番目のものを選択すると、インターフェース自体が壊れて、同じことをするはずのメソッドが異なるオブジェクトを返すようになります。

  2. ポイント

    あなたICloneable<T>は快適さを求めていますが、快適さはインターフェースが設計されたものではありません、それらの意味はオブジェクトの動作を統一することです(C#では、外部の動作、たとえばメソッドとプロパティを統一することに限定されますが、彼らの働き)。

    最初の理由でまだ納得がいかない場合は、ICloneable<T>限定的に機能するオブジェクトを作成して、cloneメソッドから返されるタイプを制限できます。ただし、厄介なプログラマはICloneable<T>、Tがそれを実装している型ではない場合でも実装できます。だから、あなたの制限を達成するために、あなたは、一般的なパラメータに素敵な制約を追加することができます:
    public interface ICloneable<T> : ICloneable where T : ICloneable<T>
    確かに、より制限せずに、1つのことをwhereあなたがまだいることを制限することはできません、Tは、インターフェイスを実装しているタイプである(あなたがから派生させることができICloneable<T>、異なる種類のそれを実装しています)。

    ご覧のとおり、この目的を達成することはできませんでした(オリジナルICloneableもこれで失敗します。実装クラスの動作を本当に制限できるインターフェイスはありません)。

ご覧のとおり、これは汎用インターフェースを完全に実装するのが困難であるだけでなく、本当に不要で役に立たないことを証明しています。

しかし、質問に戻ります。あなたが本当に求めているのは、オブジェクトを複製するときの快適さです。それには2つの方法があります。

追加の方法

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;
}

現在のCloneメソッドと衝突しないように、Copyという名前が付けられています(コンパイラーは、拡張メソッドよりも、タイプ自体の宣言されたメソッドを優先します)。制約は、(などヌルチェックを必要としない)の速度のためにそこにあります。class

これで作れない理由が明確になればいいのですがICloneable<T>。ただし、実装ICloneableしないことをお勧めします。


付録#1:ジェネリックの唯一の可能な使用ICloneable法は、Cloneメソッドのボックス化を回避できる値型であり、ボックス化されていない値があることを意味します。また、構造は(浅く)自動的に複製できるため、実装する必要はありません(ディープコピーを意味するように具体的に指定しない限り)。
IllidanS4はモニカを2014

2

質問は非常に古く(この回答を書いてから5年:)、すでに回答されていますが、この記事が質問に非常によく答えていることがわかりましたので、こちらで確認してください

編集:

質問に答える記事からの引用は次のとおりです(記事全体を必ずお読みください。他の興味深いものも含まれています)。

ICloneableについてのいくつかの考察が議論されているブラッド・エイブラムスによる2003年のブログ投稿(Microsoftで採用されていた当時)を指すインターネット上の多くの参照があります。ブログエントリは次のアドレスにあります:Implementing ICloneable。誤解を招くタイトルにもかかわらず、このブログエントリは、主に浅い/深い混乱のために、ICloneableを実装しないことを求めています。記事はまっすぐな提案で終わります。クローン作成メカニズムが必要な場合は、独自のクローン作成方法またはコピー方法を定義し、それが深いコピーか浅いコピーかを明確に文書化するようにしてください。適切なパターンは次のとおりです。public <type> Copy();


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

8
それはそれほど悪くはないように見えます class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
Nikola Novak '27

何が問題なのか本当にわかりません。どのインターフェースも実装を無理なく動作させることができません。独自のタイプに一致するICloneable<T>ように制約できたとしてもT、それが実装されたClone()ときに、それが複製されたオブジェクトにリモートで類似した何かを返すように強制されることはありません。さらに、私は1つのインターフェイスの共分散を使用している場合、それは実装するクラス持っていることが最善であり得ることを示唆しているICloneableインタフェースを持って、密閉することをICloneable<out T>含んでSelf...自分自身を返すことが期待されているプロパティを、そして
supercat

...持っている消費者は、鋳造またはに制約しますICloneable<BaseType>ICloneable<ICloneable<BaseType>>BaseType問題のは、持っている必要がありますprotectedどの実装タイプによって呼び出されますクローニングのための方法を、ICloneable。この設計は、a Container、a CloneableContainer、a FancyContainer、およびa CloneableFancyContainerを必要とする可能性を可能にします。後者は、の複製可能な派生物をContainer必要とする、またはを必要とするコードで使用できますFancyContainer(ただし、複製可能かどうかは関係ありません)。
スーパーキャット2012

私がそのような設計を支持する理由は、型を適切に複製できるかどうかの問題が、その型の他の側面とある程度直交しているためです。たとえば、FancyList適切に複製できるタイプがあり、派生物が自動的にその状態をディスクファイル(コンストラクターで指定)に永続化する場合があります。状態は変更可能なシングルトン(ファイル)の状態に関連付けられるため、派生型を複製できませんでしたFancyListが、aのほとんどの機能を必要とするが必要としない場所で派生型を使用できなくなるわけではありません。それを複製します。
スーパーキャット2012

+1-この答えは、私がここで見た中で本当に質問に答える唯一のものです。
IllidanS4はモニカを2014

0

それは非常に良い質問です...自分で作ることもできますが、

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

Andreyは、それは悪いAPIと考えられていると言いますが、このインターフェースが廃止されることについては何も聞いていません。そして、それはたくさんのインターフェースを壊します... Cloneメソッドは浅いコピーを実行するべきです。オブジェクトがディープコピーも提供する場合は、オーバーロードされたClone(bool deep)を使用できます。

編集:私がオブジェクトの「クローン」に使用するパターンは、コンストラクターでプロトタイプを渡します。

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

これにより、潜在的な冗長コードの実装状況が削除されます。ところで、ICloneableの制限について話していますが、シャロークローンまたはディープクローン、あるいは部分的にシャロー/部分的にディープクローンを実行する必要があるかどうかを決定するのは、オブジェクト自体にありませんか?オブジェクトが意図したとおりに機能する限り、本当に気にする必要がありますか?場合によっては、適切なCloneの実装には、浅い複製と深い複製の両方が含まれることがあります。


「悪い」は廃止されたという意味ではありません。それは単に「あなたはそれを使わないほうがいい」という意味です。「将来的にはICloneableインターフェースを削除する」という意味では非推奨ではありません。彼らはあなたにそれを使わないことを勧めるだけであり、ジェネリックや他の新しい機能でそれを更新しません。
jalf

デニス:マルクの答えを見てください。共分散/継承の問題により、このインターフェースは、少なくともわずかに複雑なシナリオではほとんど使用できなくなります。
Tamas Czinege 2009

確かに、私は確かにICloneableの制限を確認できます...しかし、クローン作成を使用する必要はほとんどありません。
バレッタ2009
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.