Pourquoi pas d'ICloneable <T>?

223

Y a-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.

Bluenuance
la source
6
Non! avec tout le respect dû aux «raisons», je suis d'accord avec vous, ils auraient dû le mettre en œuvre!
Shimmy Weitzhandler
Cela aurait été une bonne chose pour Microsoft d'avoir défini (le problème avec les interfaces de bricolage est que les interfaces dans deux assemblages différents seront incompatibles, même si elles sont sémantiquement identiques). Si je concevais l'interface, elle aurait trois membres, Clone, Self et CloneIfMutable, qui renverraient tous un T (le dernier membre renverrait soit Clone soit Self, selon le cas). Le membre Self permettrait d'accepter un ICloneable (de Foo) comme paramètre puis de l'utiliser comme Foo, sans besoin de transtypage.
supercat
Cela permettrait une hiérarchie de classes de clonage appropriée, où les classes héritables exposent une méthode "clone" protégée et ont des dérivés scellés qui exposent une méthode publique. Par exemple, on pourrait avoir Pile, CloneablePile: Pile, EnhancedPile: Pile et CloneableEnhancedPile: EnhancedPile, dont aucun ne serait brisé s'il était cloné (même si tous n'exposent pas une méthode de clonage publique), et FurtherEnhancedPile: EnhancedPile (qui serait brisé s'il est cloné, mais n'expose aucune méthode de clonage). Une routine qui accepte un ICloneable (de Pile) pourrait accepter un CloneablePile ou un CloneableEnhancedPile ...
supercat
... même si CloneableEnhancedPile n'hérite pas de CloneablePile. Notez que si EnhancedPile hérité de CloneablePile, FurtherEnhancedPile devrait exposer une méthode de clonage publique et pourrait être passé au code qui devrait le cloner, violant le principe de substituabilité de Liskov. Étant donné que CloneableEnhancedPile implémenterait ICloneable (Of EnhancedPile) et par implication ICloneable (Of Pile), il pourrait être transmis à une routine attendant un dérivé clonable de Pile.
supercat

Réponses:

111

ICloneable est maintenant considéré comme une mauvaise API, car il ne spécifie pas si le résultat est une copie profonde ou superficielle. 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 que cela nécessiterait un nom différent car les méthodes d'extension ont moins de priorité que les méthodes originales.

Andrey Shchekin
la source
8
Je ne suis pas d'accord, mauvais ou pas mal, c'est très utile parfois pour la connexion SHALLOW, et dans ces cas, il faut vraiment le Clone <T>, pour économiser une boxe et unboxing inutiles.
Shimmy Weitzhandler
2
@AndreyShchekin: Qu'est-ce qui n'est pas clair à propos du clonage profond vs superficiel? Si List<T>j'avais une méthode de clonage, je m'attendrais à ce qu'elle produise un List<T>dont les éléments ont les mêmes identités que ceux de la liste d'origine, mais je m'attendrais à ce que toutes les structures de données internes soient dupliquées au besoin pour garantir que rien de fait sur une liste n'affectera l' identité des éléments stockés dans l'autre. Où est l'ambiguïté? Un problème plus important avec le clonage s'accompagne d'une variante du "problème du diamant": s'il CloneableFoohérite de [non clonable publiquement] Foo, devrait CloneableDerivedFoodériver de ...
supercat
1
@supercat: Je ne peux pas dire que je comprends parfaitement vos attentes, car que considérerez-vous identitypar exemple de la liste elle-même (en cas de liste de listes)? Cependant, en ignorant cela, votre attente n'est pas la seule idée possible que les gens peuvent avoir lorsqu'ils appellent ou implémentent Clone. Que faire si les auteurs de bibliothèque qui mettent en œuvre une autre liste ne répondent pas à vos attentes? L'API doit être sans ambiguïté, sans aucun doute sans ambiguïté.
Andrey Shchekin
2
@supercat Vous parlez donc de copie superficielle. Cependant, il n'y a rien de fondamentalement mauvais avec une copie complète, par exemple si vous souhaitez effectuer une transaction réversible sur des objets en mémoire. Votre attente est basée sur votre cas d'utilisation, d'autres personnes peuvent avoir des attentes différentes. S'ils mettent vos objets dans leurs objets (ou vice versa), les résultats seront inattendus.
Andrey Shchekin
5
@supercat vous ne prenez pas en compte c'est une partie du framework .NET, pas une partie de votre application. donc si vous créez une classe qui ne fait pas de clonage en profondeur, alors quelqu'un d'autre crée une classe qui fait du clonage en profondeur et appelle Clonetoutes ses parties, cela ne fonctionnera pas de manière prévisible - selon que cette partie a été implémentée par vous ou par cette personne qui aime le clonage profond. votre point sur les modèles est valide mais avoir IMHO dans l'API n'est pas assez clair - il devrait être appelé ShallowCopypour souligner le point, ou pas du tout fourni.
Andrey Shchekin
141

En plus de la réponse de Andrey (que je suis d' accord avec, +1) - quand ICloneable est fait, vous pouvez également choisir la mise en œuvre explicite de rendre public le Clone()retour d' 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 un grand nombre d'interfaces identiques ... Comparez avec un casting ... et est-ce vraiment si mauvais?

Marc Gravell
la source
10
+1 J'ai toujours pensé que le problème de covariance était la vraie raison
Tamas Czinege
Il y a un problème avec ceci: l'implémentation de l'interface explicite doit être privée , ce qui causerait des problèmes si Foo devait à un moment donné être converti en ICloneable ... Suis-je en train de manquer quelque chose ici?
Joel à Gö
3
Mon erreur: il s'avère que, bien qu'elle soit définie comme privée, la méthode sera appelée si Foo est casté sur IClonable (CLR via C # 2nd Ed, p.319). Cela ressemble à une décision de conception étrange, mais elle est là. Donc Foo.Clone () donne la première méthode, et ((ICloneable) Foo) .Clone () donne la deuxième méthode.
Joel à Gö
En fait, les implémentations d'interfaces explicites font, à proprement parler, partie de l'API publique. En ce sens, ils peuvent être appelés publiquement via le casting.
Marc Gravell
8
La solution proposée semble excellente au début ... jusqu'à ce que vous vous rendiez compte que dans une hiérarchie de classes, vous voulez que "public Foo Clone ()" soit virtuel et remplacé dans les classes dérivées afin qu'ils puissent retourner leur propre type (et cloner leurs propres champs de cours). Malheureusement, vous ne pouvez pas changer le type de retour dans un remplacement (le problème de covariance mentionné). Donc, vous revenez au cercle d'origine à nouveau - l'implémentation explicite ne vous achète pas vraiment beaucoup (à part le retour du type de base de votre hiérarchie au lieu de `` objet ''), et vous êtes de retour pour lancer la plupart de vos Résultats de l'appel Clone (). Beurk!
Ken Beckett
18

Je dois demander, que feriez-vous exactement avec l'interface autre que mettre en œuvre? Les interfaces ne sont généralement utiles que lorsque vous y effectuez un cast (c'est-à-dire que cette classe prend-elle en charge «IBar»), ou si vous avez des paramètres ou des setters qui le prennent (c'est-à-dire que je prends un «IBar»). Avec ICloneable - nous avons parcouru l'intégralité du Framework et n'avons pas réussi à trouver un seul usage n'importe où autre chose qu'une implémentation de celui-ci. Nous n'avons également trouvé aucune utilisation dans le `` monde réel '' qui fasse autre chose que de l'implémenter (dans les ~ 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 allez-y. Vous pouvez également décider exactement ce que le «clonage» signifie pour vous (c'est-à-dire profond ou peu profond). Cependant, dans ce cas, il n'est pas nécessaire pour nous (la BCL) 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 cette abstraction entre des bibliothèques non liées.

David Kean (équipe BCL)

David Kean
la source
1
ICloneable non générique n'est pas très utile, mais ICloneable<out T>pourrait être très utile s'il hérité de ISelf<out T>, avec une seule méthode Selfde type T. On n'a pas souvent besoin de "quelque chose qui est clonable", mais on peut très bien en avoir besoin d'un Tqui est clonable. Si un objet clonable est implémenté ISelf<itsOwnType>, une routine qui a besoin d'un Tcloneable peut accepter un paramètre de type ICloneable<T>, même si tous les dérivés clonables ne Tpartagent pas un ancêtre commun.
supercat
Merci. C'est similaire à la situation de distribution que j'ai mentionnée ci-dessus (c'est-à-dire 'cette classe prend-elle en charge' IBar '). Malheureusement, nous n'avons proposé que des scénarios très limités et isolés dans lesquels vous utiliseriez réellement le fait que T est clonable. Avez-vous des situations en tête?
David Kean
Une chose qui est parfois gênante dans .net est la gestion des collections mutables lorsque le contenu d'une collection est censé être considéré comme une valeur (c'est-à-dire que je voudrai connaître plus tard l'ensemble des éléments qui se trouvent dans la collection maintenant). Quelque chose comme cela ICloneable<T>pourrait être utile pour cela, bien qu'un cadre plus large pour maintenir des classes parallèles mutables et immuables pourrait être plus utile. En d'autres termes, un code qui a besoin de voir ce que Foocontient un certain type de contenu, mais qui ne va pas le muter ni s'attendre à ce qu'il ne change jamais, pourrait utiliser un IReadableFoo, tandis que ...
supercat
... le code qui veut contenir le contenu de Foopourrait utiliser un ImmutableFoocode while qui voudrait le manipuler pourrait utiliser un MutableFoo. Le code, quel que soit le type, IReadableFoodevrait pouvoir obtenir une version modifiable ou immuable. Un tel cadre serait bien, mais malheureusement je ne trouve aucun moyen sympa de configurer les choses de manière générique. S'il y avait un moyen cohérent de créer un wrapper en lecture seule pour une classe, une telle chose pourrait être utilisée en combinaison avec ICloneable<T>pour faire une copie immuable d'une classe qui contient T'.
supercat
@supercat Si vous souhaitez cloner un List<T>, de sorte que le cloné List<T>soit une nouvelle collection contenant des pointeurs vers tous les mêmes objets de la collection d'origine, il existe deux façons simples de le faire sans ICloneable<T>. Le premier est la Enumerable.ToList()méthode d'extension: List<foo> clone = original.ToList();le second est le List<T>constructeur qui prend un IEnumerable<T>: List<foo> clone = new List<foo>(original);je soupçonne que la méthode d'extension appelle probablement juste le constructeur, mais les deux feront ce que vous demandez. ;)
CptRobby
12

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(); }
}
TcKs
la source
Toute méthode de clonage réalisable pour une classe héritable doit utiliser Object.MemberwiseClone comme point de départ (ou bien utiliser la réflexion) car sinon il n'y a aucune garantie que le clone sera du même type que l'objet d'origine. J'ai un joli motif si vous êtes intéressé.
supercat
@supercat pourquoi ne pas en faire une réponse alors?
nawfal
"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." Pouvez-vous nous donner des exemples?
Krythic
1
@Krythic ie: infrastructures de données avancées comme b-tree, red-black tree, circle buffer. En '09, il n'y avait pas de tuples, de références génériques faibles, de collections simultanées ...
TcKs
10

Il est assez facile d' écrire vous - même l'interface si vous en avez besoin:

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}
Mauricio Scheffer
la source
J'ai inclus un extrait de code car, en raison de l'honneur des droits d'
auteur,
2
Et comment exactement cette interface est-elle censée fonctionner? Pour que le résultat d'une opération de clonage puisse être converti en type T, l'objet cloné doit dériver de T. Il n'y a aucun moyen de configurer une telle contrainte de type. De façon réaliste, la seule chose que je peux voir le résultat du retour d'iCloneable est un type iCloneable.
supercat
@supercat: oui, c'est exactement ce que renvoie Clone (): un T qui implémente ICloneable <T>. MbUnit utilise cette interface depuis des années , alors oui, cela fonctionne. En ce qui concerne la mise en œuvre, jetez un œil à la source de MbUnit.
Mauricio Scheffer
2
@Mauricio Scheffer: Je vois ce qui se passe. Aucun type n'implémente réellement iCloneable (de T). Au lieu de cela, chaque type implémente iCloneable (de lui-même). Cela fonctionnerait, je suppose, bien que tout ce qu'il fait est de déplacer un transtypage. C'est dommage qu'il n'y ait aucun moyen de déclarer un champ comme ayant une contrainte d'héritage et une contrainte d'interface; il serait utile qu'une méthode de clonage renvoie un objet de type de base semi-clonable (clonage exposé en tant que méthode protégée uniquement), mais avec une contrainte de type clonable. Cela permettrait à un objet d'accepter des dérivés clonables de dérivés semi-clonables d'un type de base.
supercat
@Mauricio Scheffer: BTW, je suggérerais que ICloneable (de T) prend également en charge une propriété Self en lecture seule (de type retour T). Cela permettrait à une méthode d'accepter un ICloneable (de Foo) et de l'utiliser (via la propriété Self) comme Foo.
supercat
9

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. D'autres réponses ici fournissent de bons conseils, mais la réponse n'est toujours pas complète - pourquoi non ICloneable<T>?

  1. Usage

    Donc, vous avez une classe qui l'implémente. Alors qu'auparavant, vous aviez une méthode qui vous convenait ICloneable, elle doit maintenant être générique pour être acceptée 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 à compiler-type, vous ne pouvez pas rendre la méthode générique. Premier vrai problème.

    Vous devez donc avoir les deux ICloneable<T>et ICloneable, le premier implémentant le second. Ainsi, un implémenteur devrait implémenter les deux méthodes - object Clone()et T Clone(). Non, merci, nous avons déjà assez de plaisir avec IEnumerable.

    Comme 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>son propre type, mais il existe déjà une méthode avec la même signature (= paramètres, essentiellement) - la Clone()classe de base. Rendre explicite votre nouvelle interface de méthode de clonage est inutile, vous perdrez l'avantage que vous cherchiez lors de la création ICloneable<T>. Ajoutez donc le newmot - clé. Mais n'oubliez pas que vous devrez également remplacer la classe de base ' Clone()(l'implémentation doit rester uniforme pour toutes les classes dérivées, c'est-à-dire renvoyer le même objet de chaque méthode de clonage, donc la méthode de clone de base doit l'être virtual)! Mais, malheureusement, vous ne pouvez pas les deuxoverride etnewavec la même signature. En choisissant le premier mot clé, vous perdriez l'objectif que vous vouliez avoir lors de l'ajout ICloneable<T>. En choisissant le second, vous briseriez l'interface elle-même, ce qui rendrait les méthodes qui devraient faire la même chose renvoyer des objets différents.

  2. Point

    Vous voulez ICloneable<T>du confort, mais le confort n'est pas ce pour quoi les interfaces sont conçues, leur signification est (en général OOP) d'unifier le comportement des objets (bien qu'en C #, il 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 vous opposer à ce qui ICloneable<T>pourrait également fonctionner de manière restrictive, pour limiter le type renvoyé par la méthode de clonage. Cependant, un mauvais programmeur peut implémenter ICloneable<T>où T n'est pas le type qui l'implémente. 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>
    certainement plus restrictif que celui sans where, vous ne pouvez toujours pas restreindre que T est le type qui implémente l'interface (vous pouvez dériver ICloneable<T>de différents types qui le met en œuvre).

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

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

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

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 confort et comportement attendu aux utilisateurs, mais elle est également trop longue à mettre en œuvre. Si nous ne voulions pas que la méthode "confortable" renvoie le type actuel, il est beaucoup plus facile de l'avoir juste public virtual object Clone().

Voyons donc la solution "ultime" - qu'est-ce qui est vraiment prévu en C # pour nous rassurer?

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 s'appelle Copier pour ne pas entrer en collision avec les méthodes Clone actuelles (le compilateur préfère les méthodes déclarées du type aux extensions). La classcontrainte est là pour la vitesse (ne nécessite pas de vérification nulle, etc.).

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

IllidanS4 veut récupérer Monica
la source
Annexe # 1: La seule utilisation possible d'un générique ICloneableest pour les types de valeur, où il pourrait contourner la boxe de la méthode Clone, et cela implique que vous avez la valeur sans boîte. Et comme les structures peuvent être clonées (superficiellement) automatiquement, il n'est pas nécessaire de l'implémenter (sauf si vous précisez que cela signifie une copie complète).
IllidanS4 veut que Monica revienne
2

Bien que la question soit très ancienne (5 ans après avoir écrit ces réponses :) et a déjà été répondue, mais j'ai trouvé que cet article répond assez bien à la question, vérifiez-le ici

ÉDITER:

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 existe de nombreuses références sur Internet pointant vers un billet de blog de 2003 de Brad Abrams - à l'époque employé chez Microsoft - dans lequel certaines réflexions sur ICloneable sont discutées. L'entrée de blog se trouve à cette adresse: Implémentation d'ICloneable . Malgré le titre trompeur, cette entrée de blog appelle à ne pas implémenter ICloneable, principalement en raison 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éthodologie de clonage ou de copie et assurez-vous de documenter clairement s'il s'agit d'une copie profonde ou superficielle. Un modèle approprié est le suivant:public <type> Copy();

Sameh Deabes
la source
1

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ètres comme:

interfact IClonable<T> where T : implementing_type
sheamus
la source
8
Cela ne semble pas aussi mauvais que class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
Nikola Novak
Je ne vois pas vraiment quel est le problème. Aucune interface ne peut forcer les implémentations à se comporter raisonnablement. Même s'il ICloneable<T>pouvait contraindre Tà correspondre à son propre type, cela ne forcerait pas une implémentation de Clone()renvoyer quoi que ce soit ressemblant à distance à l'objet sur lequel il a été cloné. De plus, je suggérerais que si l'on utilise la covariance d'interface, il peut être préférable d'avoir des classes qui implémentent ICloneableêtre scellées, que l'interface ICloneable<out T>inclue une Selfpropriété qui devrait se retourner, et ...
supercat
... demander aux consommateurs de lancer ou de contraindre à ICloneable<BaseType>ou ICloneable<ICloneable<BaseType>>. La personne BaseTypeen question devrait avoir une protectedméthode de clonage, qui serait appelée par le type qui implémente ICloneable. Cette conception permettrait la possibilité que l'on souhaite avoir un Container, un CloneableContainer, un FancyContaineret un CloneableFancyContainer, ce dernier étant utilisable dans du code qui nécessite un dérivé clonable de Containerou qui nécessite un FancyContainer(mais peu importe s'il est clonable).
supercat
La raison pour laquelle je favoriserais une telle conception est que la question de savoir si un type peut être cloné de manière raisonnable est quelque peu orthogonale à d'autres aspects de celui-ci. Par exemple, on pourrait avoir un FancyListtype qui pourrait être cloné de manière raisonnable, mais un dérivé pourrait automatiquement conserver son état dans un fichier disque (spécifié dans le constructeur). Le type dérivé n'a pas pu être cloné, car son état serait attaché à celui d'un singleton mutable (le fichier), mais cela ne devrait pas empêcher l'utilisation du type dérivé dans des endroits qui nécessitent la plupart des fonctionnalités d'un FancyListmais pas pour le cloner.
supercat
+1 - cette réponse est la seule de celles que j'ai vues ici qui répond vraiment à la question.
IllidanS4 veut que Monica revienne
0

C'est une très bonne question ... Vous pouvez faire la vôtre, cependant:

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

Andrey dit que c'est considéré comme une mauvaise API, mais je n'ai rien entendu à propos de la dépréciation de cette interface. Et cela casserait des tonnes d'interfaces ... La méthode Clone devrait effectuer une copie superficielle. Si l'objet fournit également une copie complète, 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 toutes les situations d'implémentation de code redondantes potentielles. BTW, en parlant des limites d'ICloneable, n'est-ce pas vraiment à l'objet lui-même de décider si un clone peu profond ou un clone profond, ou même un clone partiellement superficiel / partiellement profond, doit être effectué? Faut-il vraiment s'en soucier, tant que l'objet fonctionne comme prévu? Dans certaines occasions, une bonne implémentation de Clone pourrait très bien inclure à la fois un clonage superficiel et profond.

baretta
la source
"mauvais" ne signifie pas obsolète. Cela signifie simplement "vous feriez mieux de ne pas l'utiliser". Il n'est pas déconseillé dans le sens où "nous supprimerons l'interface ICloneable à l'avenir". Ils vous encouragent simplement à ne pas l'utiliser et ne le mettront pas à jour avec des génériques ou d'autres nouvelles fonctionnalités.
jalf
Dennis: Voir les réponses de Marc. Les problèmes de covariance / héritage rendent cette interface à peu près inutilisable pour tout scénario qui est au moins marginalement compliqué.
Tamas Czinege
Oui, je peux voir les limites d'ICloneable, c'est sûr ... J'ai rarement besoin d'utiliser le clonage, cependant.
baretta