ジェネリックICloneable<T>
が存在しない特別な理由はありますか?
クローンを作成するたびにキャストする必要がない場合は、はるかに快適です。
ジェネリックICloneable<T>
が存在しない特別な理由はありますか?
クローンを作成するたびにキャストする必要がない場合は、はるかに快適です。
回答:
ICloneableは、結果が深いコピーであるか浅いコピーであるかを指定しないため、現在は不正なAPIと見なされています。これが彼らがこのインターフェースを改善しない理由だと思います。
型指定された複製拡張メソッドを実行できるかもしれませんが、拡張メソッドは元のメソッドよりも優先度が低いため、別の名前が必要になると思います。
List<T>
クローンメソッドがあった場合List<T>
、元のリストと同じIDを持つアイテムが生成されることを期待しますが、1つのリストに対して何も行われないようにするために、内部データ構造が必要に応じて複製されることを期待します他に保存されているアイテムのID。あいまいさはどこにありますか?クローン作成のより大きな問題には、「ダイヤモンドの問題」のバリエーションが伴います。CloneableFoo
[パブリッククローンではない]から継承する場合Foo
、CloneableDerivedFoo
派生元は...
identity
たとえばリスト(リストのリストの場合)自体について何を検討するのかなど、期待を完全に理解しているとは言えませんか?ただし、それを無視すると、を呼び出したり実装したりするときに考えられるのは、あなたの期待だけではありませんClone
。他のリストを実装しているライブラリ作成者があなたの期待に沿わない場合はどうなりますか?APIは、明白であり、間違いなく明確でなければなりません。
Clone
行い、そのすべてのパーツを呼び出すクラスを作成すると、そのパーツが自分とその人物のどちらによって実装されたかに応じて、予測どおりに機能しません。ディープクローニングが好きな人。パターンに関するあなたのポイントは有効ですが、APIにIMHOがあることは十分に明確ではありませんShallowCopy
。ポイントを強調するために呼び出す必要があるか、まったく提供されません。
アンドレイの回答に加えて、(私は同意する、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>
?すぐに多くの同一のインターフェースの実装を開始します...キャストと比較して...そしてそれは本当にとても悪いですか?
私は質問する必要があります。それを実装する以外に、インターフェイスを正確にどうしますか?インターフェイスは通常、キャストする(つまり、このクラスが 'IBar'をサポートする)か、それを受け取るパラメーターまたはセッター(つまり、 'IBar'を受け取る)がある場合にのみ役立ちます。ICloneableを使用した場合-フレームワーク全体を調べたところ、その実装以外の場所で単一の使用法を見つけることができませんでした。また、それを実装する以外の何かを実行する「現実の世界」での使用法を見つけることができませんでした(アクセスできる〜60,000のアプリで)。
ここで、「クローン可能な」オブジェクトに実装させたいパターンを適用したいだけであれば、それは完全に適切な使用法です。また、「クローニング」が自分にとって何を意味するか(つまり、深いか浅いか)を正確に決定することもできます。ただし、その場合、私たち(BCL)が定義する必要はありません。BCLで抽象化を定義するのは、関係のないライブラリ間でその抽象化として型指定されたインスタンスを交換する必要がある場合のみです。
デビッドキーン(BCLチーム)
ICloneable<out T>
から継承しISelf<out T>
、Self
タイプが1つのメソッドである場合は非常に有用ですT
。多くの場合、「複製可能なもの」は必要ありませんが、複製可能なものが必要な場合もありT
ます。複製可能なオブジェクトがを実装している場合、複製可能ISelf<itsOwnType>
なを必要とするルーチンは、T
の複製可能なICloneable<T>
派生物のすべてがT
共通の祖先を共有していなくても、タイプのパラメータを受け入れることができます。
ICloneable<T>
が役に立つかもしれませんが、並列の可変クラスと不変クラスを維持するためのより広いフレームワークがより役立つかもしれません。言い換えれば、必要なコードはいくつかの種類がものを見るためにFoo
含まれていますが、どちらもそれを変異させず、それは今までに変更が使用できないことを期待するつもりはありませんIReadableFoo
...、一方で
Foo
はImmutableFoo
whileを使用でき、それを操作したいコードはを使用できますMutableFoo
。任意のタイプのコードIReadableFoo
は、可変バージョンまたは不変バージョンのいずれかを取得できる必要があります。そのようなフレームワークはいいですが、残念ながら、一般的な方法で設定するための良い方法を見つけることができません。クラスの読み取り専用ラッパーを作成する一貫した方法がある場合、そのようなものをと組み合わせて使用しICloneable<T>
て、T
' を保持するクラスの不変のコピーを作成できます。
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);
私は、拡張メソッドは、おそらくコンストラクタを呼び出している疑いがあるが、これらの両方は、あなたが要求しているものを行います。;)
「なぜ」という質問は不要だと思います。多くのインターフェース/クラス/などがあります...これは非常に便利ですが、.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>
。編集する必要があります。
次に、オブジェクトかどうかをチェックするメソッドを取得できますis ICloneable
。今何?できません。is ICloneable<>
コンパイルタイプではオブジェクトのタイプがわからないため、メソッドをジェネリックにすることはできません。最初の本当の問題。
したがって、ICloneable<T>
との両方が必要ですICloneable
。前者は後者を実装します。したがって、実装者は- object Clone()
との両方のメソッドを実装する必要がありT Clone()
ます。いいえ、ありがとうございIEnumerable
ます。すでにで十分に楽しんでいます。
すでに指摘したように、継承の複雑さもあります。共分散がこの問題を解決するように見えるかもしれませんが、派生型ICloneable<T>
はそれ自体の型を実装する必要がありますが、基本Clone()
クラスの-同じシグニチャー(=パラメーター、基本的に)を持つメソッドがすでにあります。新しいクローンメソッドインターフェースを明示的にしても意味がありませんICloneable<T>
。作成時に求めていた利点が失われます。したがって、new
キーワードを追加します。ただし、基本クラスもオーバーライドする必要があることを忘れないでくださいClone()
(すべての派生クラスで実装を統一する必要があります。つまり、すべてのクローンメソッドから同じオブジェクトを返すため、基本クローンメソッドはでなければなりませんvirtual
)。しかし、残念ながら、両方override
とnew
同じシグネチャを持つメソッド。最初のキーワードを選択すると、追加時に目標を失うことになりますICloneable<T>
。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
しないことをお勧めします。
ICloneable
法は、Cloneメソッドのボックス化を回避できる値型であり、ボックス化されていない値があることを意味します。また、構造は(浅く)自動的に複製できるため、実装する必要はありません(ディープコピーを意味するように具体的に指定しない限り)。
質問は非常に古く(この回答を書いてから5年:)、すでに回答されていますが、この記事が質問に非常によく答えていることがわかりましたので、こちらで確認してください
編集:
質問に答える記事からの引用は次のとおりです(記事全体を必ずお読みください。他の興味深いものも含まれています)。
ICloneableについてのいくつかの考察が議論されているブラッド・エイブラムスによる2003年のブログ投稿(Microsoftで採用されていた当時)を指すインターネット上の多くの参照があります。ブログエントリは次のアドレスにあります: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
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
ように制約できたとしてもT
、それが実装されたClone()
ときに、それが複製されたオブジェクトにリモートで類似した何かを返すように強制されることはありません。さらに、私は1つのインターフェイスの共分散を使用している場合、それは実装するクラス持っていることが最善であり得ることを示唆しているICloneable
インタフェースを持って、密閉することをICloneable<out T>
含んでSelf
...自分自身を返すことが期待されているプロパティを、そして
ICloneable<BaseType>
かICloneable<ICloneable<BaseType>>
。BaseType
問題のは、持っている必要がありますprotected
どの実装タイプによって呼び出されますクローニングのための方法を、ICloneable
。この設計は、a Container
、a CloneableContainer
、a FancyContainer
、およびa CloneableFancyContainer
を必要とする可能性を可能にします。後者は、の複製可能な派生物をContainer
必要とする、またはを必要とするコードで使用できますFancyContainer
(ただし、複製可能かどうかは関係ありません)。
FancyList
適切に複製できるタイプがあり、派生物が自動的にその状態をディスクファイル(コンストラクターで指定)に永続化する場合があります。状態は変更可能なシングルトン(ファイル)の状態に関連付けられるため、派生型を複製できませんでしたFancyList
が、aのほとんどの機能を必要とするが必要としない場所で派生型を使用できなくなるわけではありません。それを複製します。
それは非常に良い質問です...自分で作ることもできますが、
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Andreyは、それは悪いAPIと考えられていると言いますが、このインターフェースが廃止されることについては何も聞いていません。そして、それはたくさんのインターフェースを壊します... Cloneメソッドは浅いコピーを実行するべきです。オブジェクトがディープコピーも提供する場合は、オーバーロードされたClone(bool deep)を使用できます。
編集:私がオブジェクトの「クローン」に使用するパターンは、コンストラクターでプロトタイプを渡します。
class C
{
public C ( C prototype )
{
...
}
}
これにより、潜在的な冗長コードの実装状況が削除されます。ところで、ICloneableの制限について話していますが、シャロークローンまたはディープクローン、あるいは部分的にシャロー/部分的にディープクローンを実行する必要があるかどうかを決定するのは、オブジェクト自体にありませんか?オブジェクトが意図したとおりに機能する限り、本当に気にする必要がありますか?場合によっては、適切なCloneの実装には、浅い複製と深い複製の両方が含まれることがあります。