Question

Y at-il une raison particulière pour laquelle un ICloneable<T> générique n'existe pas?

Il serait beaucoup plus à l'aise, si je ne aurais pas besoin de le jeter à chaque fois que je clone quelque chose.

Était-ce utile?

La solution

ICloneable est considéré comme une mauvaise API maintenant, car il ne précise pas si le résultat est une profonde ou une copie superficielle. Je pense que c'est la raison pour laquelle ils n'améliorent pas cette interface.

Vous pouvez probablement faire une méthode d'extension clonage tapé, mais je pense qu'il aurait besoin d'un nom différent puisque les méthodes d'extension ont moins d'importance que ceux d'origine.

Autres conseils

En plus de la réponse de Andrey (que je suis d'accord avec, +1) - lorsque ICloneable est fait, vous pouvez également choisir la mise en œuvre explicite de faire la Clone() publique retourner un objet typé:

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

Bien sûr, il y a un deuxième problème avec un ICloneable<T> générique -. Héritage

Si j'ai:

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

Et je ICloneable<T> mis en œuvre, puis-je mettre en œuvre ICloneable<Foo>? ICloneable<Bar>? Vous commencez rapidement mettre en œuvre un grand nombre d'interfaces identiques ... Comparer à un casting ... et est-il vraiment si mauvais?

Je dois demander, que feriez-vous exactement avec l'interface autre que mettre en œuvre? Les interfaces sont généralement utiles que lorsque vous lancez à (ie ce soutien ne classe BIRA), ou avoir des paramètres ou setters qui le prennent (c.-à-je prendre un BIRA). Avec ICloneable - nous sommes allés dans l'ensemble du Cadre et réussi à trouver une seule utilisation partout qui était autre chose qu'une mise en œuvre de celui-ci. Nous avons également réussi à trouver une utilisation dans le « monde réel » qui fait aussi autre chose que la mise en œuvre (dans les ~ 60.000 applications que nous avons accès à).

Maintenant, si vous voulez simplement appliquer un modèle que vous voulez que vos objets « Cloneable » à mettre en œuvre, c'est un usage tout à fait bien - et aller de l'avant. Vous pouvez également décider exactement ce que « clonage » signifie pour vous (profonde ou peu profonde). Cependant, dans ce cas, il n'y a pas besoin de nous (la BCL) pour le définir. Nous définissons que des abstractions dans la BCL quand il est nécessaire d'échanger des cas typés comme cette abstraction entre bibliothèques indépendantes.

David Kean (BCL Team)

Je pense que la question « pourquoi » est inutile. Il y a beaucoup d'interfaces / classes / etc ... qui est très utile, mais ne fait pas partie de la bibliothèque de base .NET Frameworku.

Mais, surtout, vous pouvez le faire vous-même.

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

Il est assez facile de écrire le vous interfacer si vous en avez besoin:

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

Après avoir lu récemment l'article Pourquoi Copie d'un objet est une chose terrible à faire? Je pense que cette question a besoin clafirication supplémentaire. D'autres réponses ici fournissent de bons conseils, mais la réponse est incomplète - pourquoi pas ICloneable<T>

  1. Utilisation

    Alors, vous avez une classe qui implémente. Alors que vous avez déjà eu une méthode qui voulait ICloneable, il doit maintenant être générique d'accepter ICloneable<T>. Vous devrez le modifier.

    Alors, vous auriez pu obtenu une méthode qui vérifie si un is ICloneable objet. Et maintenant? Vous ne pouvez pas faire is ICloneable<> et que vous ne connaissez pas le type de l'objet à la compilation de type, vous ne pouvez pas faire la méthode générique. Le premier problème réel.

    Vous avez donc besoin d'avoir les deux ICloneable<T> et ICloneable, l'ancienne mise en œuvre de ce dernier. Ainsi, un devrait implémenteur mettre en œuvre les deux méthodes - object Clone() et T Clone(). Non, merci, nous avons déjà assez de plaisir avec IEnumerable.

    Comme nous l'avons souligné, il y a aussi la complexité de l'héritage. Alors que covariance peut sembler résoudre ce problème, un type dérivé doit mettre en œuvre ICloneable<T> de son propre type, mais il existe déjà une méthode avec la même signature (= paramètres, essentiellement) - la Clone() de la classe de base. Rendre votre nouvelle interface de méthode clone explicite est inutile, vous perdrez l'avantage lors de la création vous avez demandé ICloneable<T>. ajouter Donc le mot-clé new. Mais ne pas oublier que vous devez également remplacer la classe de base Clone() (la mise en œuvre doit rester uniforme pour toutes les classes dérivées, à savoir retourner le même objet de chaque méthode clone, la méthode clone de base doit être virtual) ! Mais, malheureusement, vous ne pouvez pas les deux méthodes de override et new avec la même signature. Le choix du premier mot-clé, vous perdriez l'objectif que vous vouliez avoir lors de l'ajout ICloneable<T>. Chossing le second, vous brisaient l'interface elle-même, ce qui rend les méthodes qui devraient faire le même retour des objets différents.

  2. point

    Vous voulez ICloneable<T> pour le confort, mais le confort est pas ce que les interfaces sont conçues pour, leur sens est (en POO général) d'unifier le comportement des objets (bien en C #, il est limité à unifier le comportement extérieur, par exemple les méthodes et propriétés, pas leur fonctionnement).

    Si la première raison n'a pas convaincu vous encore, vous pourriez objecter que ICloneable<T> pourrait également travailler limitativement, pour limiter le type de retour de la méthode clone. Cependant, programmeur méchant peut mettre en œuvre ICloneable<T> où T est pas le type qui est mise en œuvre. Donc, pour atteindre votre restriction, vous pouvez ajouter une belle contrainte au paramètre générique:
    public interface ICloneable<T> : ICloneable where T : ICloneable<T>
    Certes, plus restrictive que celle sans where, vous ne pouvez toujours pas limiter ce T est le type qui implémente l'interface (vous pouvez tirer de ICloneable<T> de type différent qui l'implémente).

    Vous voyez, même cet effet n'a pas pu être atteint (le ICloneable d'origine échoue également à cela, pas d'interface peut vraiment limiter le comportement de la classe d'exécution).

Comme vous pouvez le voir, cela prouve fait l'interface générique est à la fois difficile à mettre en œuvre pleinement et aussi vraiment inutile et inutile.

Mais revenons à la question, ce que vous cherchez vraiment est d'avoir le confort lors du clonage d'un objet. Il y a deux façons de le faire:

Méthodes supplémentaires

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

Cette solution fournit à la fois le confort et le comportement destiné aux utilisateurs, mais il est aussi trop long à mettre en œuvre. Si nous ne voulions pas avoir la méthode « confortable » retourner le type actuel, il est beaucoup plus facile d'avoirjuste public virtual object Clone().

Voyons donc la solution « ultime » - ce en C # est vraiment intented pour nous donner le confort?

Méthodes d'extension!

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

Il est nommé Copier ne pas entrer en collision avec le courant Clone méthodes (compilateur préfère les méthodes propres déclarées du Ecrasez les d'extension). La contrainte de class est là pour la vitesse (ne nécessite pas vérifier null etc.).

J'espère que cela clarifie la raison pour laquelle de ne pas faire ICloneable<T>. Cependant, il est recommandé de ne pas mettre en œuvre ICloneable du tout.

Bien que la question est très ancienne (5 ans d'écrire cette réponse :) et a déjà répondu, mais je trouve cet article répond à la question tout à fait bien, vérifier ici

EDIT:

Voici la citation de l'article qui répond à la question (assurez-vous de lire l'article complet, il comprend d'autres choses intéressantes):

  

Il y a beaucoup de références sur le pointage Internet à un billet de blog 2003   par Brad Abrams - au moment employé chez Microsoft - dans lequel certains   réflexions sur ICloneable sont discutées. L'entrée de blog peut être trouvé   à l'adresse suivante: mise en œuvre ICloneable . En dépit de la confusion   titre, cette entrée de blog appelle à ne pas mettre en œuvre ICloneable, principalement   à cause de la confusion peu profonde / profonde. L'article se termine par une ligne droite   Suggestion: Si vous avez besoin d'un mécanisme de clonage, définir votre propre clone, ou   Copier la méthodologie, et assurez-vous de documenter clairement si elle est   copie profonde ou peu profonde. Un motif approprié est: public <type> Copy();

Un gros problème est qu'ils ne pouvaient pas limiter T être la même classe. exemple Fore ce qui vous empêcherait de faire ceci:

interface IClonable<T>
{
    T Clone();
}

class Dog : IClonable<JackRabbit>
{
    //not what you would expect, but possible
    JackRabbit Clone()
    {
        return new JackRabbit();
    }

}

Ils ont besoin d'une restriction des paramètres comme:

interfact IClonable<T> where T : implementing_type

Il est une très bonne question ... Vous pouvez faire votre propre, bien que:

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

Andrey dit qu'il est considéré comme une mauvaise API, mais je n'ai pas entendu quoi que ce soit au sujet de cette interface devenir obsolète. Et cela briserait tonnes d'interfaces ... La méthode Clone doit effectuer une copie superficielle. Si l'objet fournit également une copie en profondeur, un clone surchargé (bool profond) peut être utilisé.

EDIT:. Motif i utilisation de "clonage" un objet, passe un prototype dans le constructeur

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

Cela supprime toutes les situations de mise en œuvre du code redondant potentiels. BTW, parler des limites de ICloneable, ne l'est pas vraiment à l'objet lui-même de décider si un clone peu profond ou clone profond, ou même un clone en partie peu profonde / partie profonde, doivent être effectués? Faut-il vraiment prendre soin, tant que l'objet fonctionne comme prévu? Dans certaines occasions, une bonne mise en œuvre du clone pourrait très bien inclure à la fois le clonage superficiel et profond.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top