web-dev-qa-db-ja.com

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

汎用のICloneable<T> 存在しない?

何かをクローンするたびにキャストする必要がなければ、ずっと快適です。

205
Bluenuance

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

型付きクローン拡張メソッドをおそらく実行できますが、拡張メソッドは元のメソッドより優先順位が低いため、別の名前が必要になると思います。

106
Andrey Shchekin

Andreyの返信(私は同意します、+ 1)に加えて-ICloneableis完了したら、明示的な実装を選択してpublic Clone()を返すこともできます型付きオブジェクト:

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

もちろん、一般的なICloneable<T>-継承に関する2番目の問題があります。

私が持っている場合:

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

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

136
Marc Gravell

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

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

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

18
David Kean

「なぜ」という質問は不要だと思います。多くのインターフェース/クラス/などがあり、非常に便利ですが、.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(); }
}
12
TcKs

自分でインターフェースを書く 必要な場合はとても簡単です:

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

最近記事を読んだ後 なぜオブジェクトをコピーするのが恐ろしいことなのか? 、この質問にはさらに詳しい説明が必要だと思います。ここの他の回答は良いアドバイスを提供しますが、それでも答えは完全ではありません-なぜ_ICloneable<T>_がないのですか?

  1. 使用法

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

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

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

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

  2. ポイント

    快適さのために_ICloneable<T>_が必要ですが、快適さはインターフェイスの設計対象ではなく、その意味は(一般的にOOP)オブジェクトの動作を統一することです(ただし、C#では、外側の動作の統一に限定されます。メソッドとプロパティ、それらの働きではありません)。

    最初の理由でまだ確信が持てない場合は、_ICloneable<T>_も制限的に機能し、cloneメソッドから返される型を制限できることに反対することができます。ただし、厄介なプログラマーは_ICloneable<T>_を実装できますが、Tはそれを実装している型ではありません。したがって、制限を実現するために、汎用パラメーターにNice制約を追加できます。
    _public interface ICloneable<T> : ICloneable where T : ICloneable<T>_
    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制約は速度のためにあります(nullチェックなどを必要としません)。

_ICloneable<T>_を作成しない理由がこれで明らかになることを願っています。ただし、ICloneableをまったく実装しないことをお勧めします。

8
IllidanS4

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

編集:

ここに質問に答える記事からの引用があります(記事全体を読むようにしてください、それは他の興味深いことを含みます):

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

2
Sameh Deabes

大きな問題は、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
1
sheamus

それは非常に良い質問です...しかし、あなたはあなた自身を作ることができます:

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

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

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

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

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

0
baretta