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.
c#
.net
icloneable
Bluenuance
la source
la source
Réponses:
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.
la source
List<T>
j'avais une méthode de clonage, je m'attendrais à ce qu'elle produise unList<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'ilCloneableFoo
hérite de [non clonable publiquement]Foo
, devraitCloneableDerivedFoo
dériver de ...identity
par 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émententClone
. 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é.Clone
toutes 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éShallowCopy
pour souligner le point, ou pas du tout fourni.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 leClone()
retour d' un objet typé:Bien sûr, il y a un deuxième problème avec un générique
ICloneable<T>
héritage .Si j'ai:
Et j'ai implémenté
ICloneable<T>
, puis-je implémenterICloneable<Foo>
?ICloneable<Bar>
? Vous commencez rapidement à implémenter un grand nombre d'interfaces identiques ... Comparez avec un casting ... et est-ce vraiment si mauvais?la source
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)
la source
ICloneable<out T>
pourrait être très utile s'il hérité deISelf<out T>
, avec une seule méthodeSelf
de typeT
. On n'a pas souvent besoin de "quelque chose qui est clonable", mais on peut très bien en avoir besoin d'unT
qui est clonable. Si un objet clonable est implémentéISelf<itsOwnType>
, une routine qui a besoin d'unT
cloneable peut accepter un paramètre de typeICloneable<T>
, même si tous les dérivés clonables neT
partagent pas un ancêtre commun.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 queFoo
contient un certain type de contenu, mais qui ne va pas le muter ni s'attendre à ce qu'il ne change jamais, pourrait utiliser unIReadableFoo
, tandis que ...Foo
pourrait utiliser unImmutableFoo
code while qui voudrait le manipuler pourrait utiliser unMutableFoo
. Le code, quel que soit le type,IReadableFoo
devrait 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 avecICloneable<T>
pour faire une copie immuable d'une classe qui contientT
'.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 sansICloneable<T>
. Le premier est laEnumerable.ToList()
méthode d'extension:List<foo> clone = original.ToList();
le second est leList<T>
constructeur qui prend unIEnumerable<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. ;)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.
la source
Il est assez facile d' écrire vous - même l'interface si vous en avez besoin:
la source
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>
?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éeICloneable<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 faireis 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>
etICloneable
, le premier implémentant le second. Ainsi, un implémenteur devrait implémenter les deux méthodes -object Clone()
etT Clone()
. Non, merci, nous avons déjà assez de plaisir avecIEnumerable
.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) - laClone()
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éationICloneable<T>
. Ajoutez donc lenew
mot - 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'êtrevirtual
)! Mais, malheureusement, vous ne pouvez pas les deuxoverride
etnew
avec la même signature. En choisissant le premier mot clé, vous perdriez l'objectif que vous vouliez avoir lors de l'ajoutICloneable<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.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émenterICloneable<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ériverICloneable<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
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!
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
class
contrainte 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 mettreICloneable
en œuvre du tout.la source
ICloneable
est 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).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):
la source
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:
Ils ont besoin d'une restriction de paramètres comme:
la source
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
pouvait contraindreT
à correspondre à son propre type, cela ne forcerait pas une implémentation deClone()
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émententICloneable
être scellées, que l'interfaceICloneable<out T>
inclue uneSelf
propriété qui devrait se retourner, et ...ICloneable<BaseType>
ouICloneable<ICloneable<BaseType>>
. La personneBaseType
en question devrait avoir uneprotected
méthode de clonage, qui serait appelée par le type qui implémenteICloneable
. Cette conception permettrait la possibilité que l'on souhaite avoir unContainer
, unCloneableContainer
, unFancyContainer
et unCloneableFancyContainer
, ce dernier étant utilisable dans du code qui nécessite un dérivé clonable deContainer
ou qui nécessite unFancyContainer
(mais peu importe s'il est clonable).FancyList
type 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'unFancyList
mais pas pour le cloner.C'est une très bonne question ... Vous pouvez faire la vôtre, cependant:
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.
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.
la source