web-dev-qa-db-fra.com

Pourquoi pas <T>?

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

Ce serait beaucoup plus confortable si je n'avais pas besoin de le lancer à chaque fois que je clone quelque chose.

205
Bluenuance

ICloneable est maintenant considéré comme une mauvaise API, car elle ne spécifie pas si le résultat est une copie profonde ou peu profonde. Je pense que c'est pourquoi ils n'améliorent pas cette interface.

Vous pouvez probablement faire une méthode d'extension de clonage typée, mais je pense qu'elle nécessiterait un nom différent car les méthodes d'extension ont moins de priorité que les méthodes d'origine.

106
Andrey Shchekin

En plus de la réponse d'Andrey (avec lequel je suis d'accord, +1) - lorsque ICloneableest terminé, vous pouvez également choisir une implémentation explicite pour rendre public Clone() return 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 générique ICloneable<T> - héritage.

Si j'ai:

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

Et j'ai implémenté ICloneable<T>, puis-je implémenter ICloneable<Foo>? ICloneable<Bar>? Vous commencez rapidement à implémenter de nombreuses interfaces identiques ... Comparez à un casting ... et est-ce si grave?

136
Marc Gravell

Je dois demander, que feriez-vous exactement avec l'interface autre que l'implémenter? Les interfaces ne sont généralement utiles que lorsque vous transtypez-le (c'est-à-dire que cette classe prend en charge "IBar") ou que des paramètres ou des paramètres le prennent (c'est-à-dire que je prends un "IBar"). Avec ICloneable - nous avons parcouru l’intégralité de la structure et n’avons pas réussi à trouver une utilisation unique autre que son implémentation. Nous n’avons pas non plus réussi à trouver d’utilisation dans le "monde réel" qui fasse également autre chose que de l’implémenter (dans les quelque 60 000 applications auxquelles nous avons accès).

Maintenant, si vous souhaitez simplement appliquer un modèle que vous souhaitez que vos objets 'clonables' implémentent, c'est une utilisation tout à fait correcte - et continuez. Vous pouvez également décider exactement de ce que le "clonage" signifie pour vous (c'est-à-dire profond ou peu profond). Cependant, dans ce cas, nous (la BCL) n’avons pas besoin de le définir. Nous ne définissons les abstractions dans la BCL que lorsqu'il est nécessaire d'échanger des instances typées comme étant cette abstraction entre bibliothèques non liées.

David Kean (équipe BCL)

18
David Kean

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

Mais, principalement, 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(); }
}
12
TcKs

C'est assez facile de écrivez vous-même l'interface si vous en avez besoin:

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

Ayant lu récemment l'article Pourquoi copier un objet est une chose terrible à faire? , je pense que cette question nécessite une clafirication supplémentaire. Les autres réponses fournies ici donnent de bons conseils, mais la réponse n’est pas encore complète - pourquoi pas ICloneable<T>?

  1. Utilisation

    Donc, vous avez une classe qui l'implémente. Tandis que précédemment vous aviez une méthode qui voulait ICloneable, elle doit maintenant être générique pour accepter ICloneable<T>. Vous auriez besoin de le modifier.

    Ensuite, vous auriez pu avoir une méthode qui vérifie si un objet is ICloneable. Et maintenant? Vous ne pouvez pas faire is ICloneable<> Et comme vous ne connaissez pas le type de l'objet au type de compilation, vous ne pouvez pas rendre la méthode générique. Premier vrai problème.

    Il faut donc avoir à la fois ICloneable<T> Et ICloneable, le premier implémentant le dernier. Ainsi, un implémenteur devrait implémenter les deux méthodes - object Clone() et T Clone(). Non, merci, nous nous sommes déjà assez amusés avec IEnumerable.

    Comme nous l'avons déjà souligné, il y a aussi la complexité de l'héritage. Bien que la covariance puisse sembler résoudre ce problème, un type dérivé doit implémenter ICloneable<T> De son propre type, mais il existe déjà une méthode avec la même signature (= paramètres, en gros) - la Clone() de la classe de base. Rendre explicite votre nouvelle interface de méthode de clonage est inutile, vous perdrez l'avantage que vous recherchiez lors de la création de ICloneable<T>. Ajoutez donc le mot clé new. Mais n'oubliez pas que vous devrez également redéfinir la classe de base 'Clone() _ (l'implémentation doit rester uniforme pour toutes les classes dérivées, c'est-à-dire pour renvoyer le même objet à partir de chaque méthode de clonage, le clone de base La méthode doit être virtual)! Mais, malheureusement, vous ne pouvez pas utiliser les mêmes méthodes override et new avec la même signature. En choisissant le premier mot-clé, vous perdriez le but que vous vouliez avoir en ajoutant ICloneable<T>. En chossant le second, vous casseriez l'interface elle-même en faisant des méthodes qui devraient faire la même chose en retournant des objets différents.

  2. Point

    Vous voulez ICloneable<T> Pour votre confort, mais le confort n’est pas l’objet des interfaces; leur signification est (en général, la POO) d’unifier le comportement des objets (bien qu’en C #, elle se limite à unifier le comportement extérieur, par exemple les méthodes et les propriétés, pas leur fonctionnement).

    Si la première raison ne vous a pas encore convaincu, vous pouvez objecter que ICloneable<T> Peut également fonctionner de manière restrictive, afin de limiter le type renvoyé par la méthode clone. Cependant, un mauvais programmeur peut implémenter ICloneable<T> Où T n’est pas le type qui l’implémente. Donc, pour réaliser votre restriction, vous pouvez ajouter une contrainte de Nice au paramètre générique:
    public interface ICloneable<T> : ICloneable where T : ICloneable<T>
    Certainement plus restrictif que celui sans where, vous ne pouvez toujours pas restreindre cela [~ # ~] t [~ # ~] est le type qui implémente l'interface (vous pouvez dériver de ICloneable<T> d'un type différent qui l'implémente).

    Vous voyez, même cet objectif n'a pas pu être atteint (le ICloneable d'origine échoue également, aucune interface ne peut réellement limiter le comportement de la classe d'implémentation).

Comme vous pouvez le constater, cela prouve que l’interface générique est à la fois difficile à implémenter complètement et vraiment inutile et inutile.

Mais revenons à la question, ce que vous recherchez réellement, c’est de vous réconforter 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 offre à la fois un confort et un comportement voulu aux utilisateurs, mais elle est également trop longue à mettre en œuvre. Si nous ne voulions pas que la méthode "comfortable" retourne le type actuel, il est beaucoup plus facile d'avoir juste public virtual object Clone().

Voyons maintenant la solution "ultime": qu'en C #, ce qui est réellement destiné à nous réconforter?

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 les méthodes Clone actuelles (le compilateur préfère les méthodes déclarées par le type plutôt que celles par extension). La contrainte class existe pour la vitesse (ne nécessite pas de contrôle nul, etc.).

J'espère que cela clarifie la raison pour laquelle vous ne faites pas ICloneable<T>. Cependant, il est recommandé de ne pas implémenter ICloneable.

8
IllidanS4

Bien que la question soit très ancienne (5 ans après avoir écrit cela répond :) et on l’a déjà répondu, mais j’ai trouvé que cet article répond assez bien à la question, vérifiez-le ici

EDIT:

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

Il existe de nombreuses références sur Internet qui renvoient à un article de Brad Abrams publié en 2003 sur un blog, alors employé par Microsoft, dans lequel certaines réflexions sur ICloneable sont discutées. L’entrée du blog se trouve à l’adresse suivante: Implementing ICloneable . Malgré le titre trompeur, cette entrée de blog appelle à ne pas mettre en œuvre ICloneable, principalement à cause d'une confusion superficielle/profonde. L'article se termine par une suggestion simple: Si vous avez besoin d'un mécanisme de clonage, définissez votre propre méthode de clonage ou de copie et assurez-vous de documenter clairement s'il s'agit d'une copie profonde ou peu profonde. Un modèle approprié est: public <type> Copy();

2
Sameh Deabes

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

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 de paramètre comme:

interfact IClonable<T> where T : implementing_type
1
sheamus

C'est une très bonne question ... Vous pouvez toutefois créer la vôtre:

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

Andreï dit que c'est considéré comme une mauvaise API, mais je n'ai jamais entendu parler de la dépréciation de cette interface. Et cela briserait des tonnes d'interfaces ... La méthode Clone devrait effectuer une copie superficielle. Si l'objet fournit également une copie en profondeur, un clone surchargé (bool deep) peut être utilisé.

EDIT: Le motif que j'utilise pour "cloner" un objet, passe un prototype dans le constructeur.

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

Cela supprime toute situation potentielle d'implémentation de code redondant. BTW, parlant des limites de ICloneable, n’est-ce pas vraiment à l’objet de décider s’il faut effectuer un clone peu profond ou un clone profond, ou même un clone en partie peu profond/en partie profond? Devrions-nous vraiment nous en soucier, tant que l'objet fonctionne comme prévu? Dans certaines circonstances, une bonne mise en œuvre de Clone peut très bien inclure un clonage à la fois superficiel et profond.

0
baretta