Pourquoi pas ICloneable<T>?
Est-il une raison particulière pour laquelle un générique ICloneable<T>
n'existe pas?
Il serait beaucoup plus à l'aise, si je n'aurai pas besoin de le lancer à chaque fois que je clone quelque chose.
- Non! avec tout le respect dû à la 'raisons', je suis d'accord avec vous, ils doivent avoir mis en place!
- Il aurait été une bonne chose pour Microsoft d'avoir défini (le problème avec brew-votre-propre interfaces interfaces dans les deux assemblées seront incompatibles, même si elles sont sémantiquement identiques). Si j'avais été la conception de l'interface, il aurait trois membres, Clone de Soi, et CloneIfMutable, qui serait de retour T (le dernier membre soit de retour Clone ou le Soi, selon le cas). L'Auto membre ferait-il possible d'accepter une ICloneable(de Foo) en tant que paramètre et ensuite l'utiliser comme un Toto, sans avoir besoin d'un transtypage.
- Ce qui permettrait une bonne clonage hiérarchie de classe, où héritables classes exposent protégés "clone" de la méthode, et ont scellé des instruments dérivés que d'exposer un établissement public. Par exemple, on pourrait avoir des Tas, CloneablePile:Pile, EnhancedPile:Pile, et CloneableEnhancedPile:EnhancedPile, aucun ne serait brisé si cloné (même si pas tous les exposer à un public méthode de clonage), et FurtherEnhancedPile:EnhancedPile (qui serait brisé si cloné, mais n'est pas d'exposer la méthode de clonage). Une routine qui accepte un ICloneable(de la Pile) pourrait accepter une CloneablePile ou un CloneableEnhancedPile...
- ...même si CloneableEnhancedPile n'hérite pas de CloneablePile. Notez que si EnhancedPile hérité de CloneablePile, FurtherEnhancedPile aurait pour exposer un public méthode de clonage et peut être transmis à un code qui attendent pour le Clone, en violation de la Liskov Substituabilité Principe. Depuis CloneableEnhancedPile serait la mise en œuvre ICloneable(De EnhancedPile) et par voie de conséquence ICloneable(De la Pile), il pourrait être passée à une routine attend à une clonable dérivé de la Pile.
Vous devez vous connecter pour publier un commentaire.
ICloneable est considéré comme un mauvais API maintenant, car il ne précise pas si le résultat est d'une profondeur ou d'une copie. Je pense que c'est pourquoi ils n'améliorent pas cette interface.
Vous pouvez probablement le faire tapé le clonage de la méthode d'extension, 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.
List<T>
avait une méthode clone, je l'espère pour donner uneList<T>
dont les éléments ont la même identité en tant que personnes dans la liste d'origine, mais je m'attends à ce que toutes les structures de données internes seraient deux fois que nécessaire pour s'assurer de ne rien faire pour une liste va affecter le identités des éléments stockés dans l'autre. Où est l'ambiguïté? Le plus gros problème avec le clonage est livré avec une variation de la "diamond problème": siCloneableFoo
hérite de [le public n'a pas clonable]Foo
, devraitCloneableDerivedFoo
de dériver à partir de...DerivedFoo
? Je dirais que génériqueICloneable<out T>
pourrait résoudre le problème si elle dérive deISelf<out T>
qui contient unSelf
propriété de typeT
qui renvoie simplement lui-même. Dans ce cas, quelqu'un qui a besoin d'un clonableDerivedFoo
pourrait accepter un paramètre de typeICloneable<DerivedFoo>
.identity
de la liste elle-même, par exemple (dans le cas d'une liste de listes)? Cependant, ignorant que, votre attente n'est pas la seule idée possible que les personnes peuvent avoir lors de l'appel ou de la mise en œuvre deClone
. Que faire si la bibliothèque des auteurs mise en œuvre de certaines autres liste ne suivez pas vos attentes? L'API doit être trivialement sans ambiguïté, sans doute pas sans ambiguïté.MemberwiseClone
sur une classe ou la copie d'une structure. Bien qu'il existe des situations où les plus profondes de la copie serait approprié, de tels scénarios entraîne un resserrement de la copie de la collection et de son contenu que de façon implicite par quelque chose comme unList<T>
, et si l'on va à ce niveau, il faut aller au-delà deClone
, à l'aide d'une interface avec les membresAsImmutable()
etAsMutable()
; appelAsImmutable
sur un objet doit donner une immuable instantané.AsImmutable
est appelé, le retour de la mise en cache instantané si il était encore bon. Même si la seule façon de savoir si la mise en cache de l'instantané est toujours valide a été pour générer un nouveau immuable de l'objet et de le comparer, de retour en cache instantané permettrait d'accélérer les comparaisons entre elle et le plus tôt un. Une bonne faible stage de collecte pourrait améliorer encore les choses.Clone
sur toutes ses parties, il ne fonctionnera pas de façon prévisible, selon qu'elle a été mise en œuvre par vous ou cette personne qui aime profondément le clonage. votre point sur les motifs, c'est valable, mais ayant à mon humble avis, l'API n'est pas assez clair -- il soit devrait être appeléShallowCopy
insiste sur ce point, ou ne sont pas fournis à tous.IList<T>
etICloneable
, etT
était un type de classe, je m'attends à ce que le clonage devrait me donner une nouvelle liste qui a tenu les références à l'objet même cas que l'original. Je peux considérer un clone fonction qui n'a rien d'autre comme rompu. Pour qu'un objet clone plus profond que son contenu sémantique n'est pas "en option"--c'est une rupture de comportement.Equals
. Une bonne relation d'équivalence permettrait de préciser que les deux objets doivent comparer "l'égalité" si et seulement si ils seraient indiscernables du point de vue sémantique, sauf par la vérification de référence de l'égalité. Avoir une relation d'équivalence disponible pour toutes les classes faciliterait les choses comme un stage de cours (étant donnés deux objets à comparer l'égalité, dont l'un est dans un stage dictionnaire, on serait capable de se substituer à l'autre en toute sécurité). Dommage que leObject.Equals
des remplacements defloat
,double
, etDecimal
ne fonctionnent pas de cette façon.En plus de Andrey réponse (dont je suis d'accord avec, +1) - lorsque
ICloneable
est fait, vous pouvez également choisir la mise en œuvre explicite de rendre le publicClone()
retour typé objet: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 mis en place
ICloneable<T>
, alors dois-je mettre en œuvreICloneable<Foo>
?ICloneable<Bar>
? Vous démarrez rapidement la mise en œuvre d'un lot à l'identique des interfaces...Comparer à un casting... et est-il vraiment si mauvais?
J'ai besoin de demander, exactement ce que voulez-vous faire avec l'interface autres que mettre en œuvre? Les Interfaces sont généralement seulement utile lorsque vous lancez pour elle (c'est à dire est-ce à l'appui de classe 'IBar"), ou ont des paramètres ou des poseurs qui le prendre (c'est à dire je prends un 'IBar'). Avec ICloneable - nous sommes allés à travers l'ensemble de la structure et a échoué à trouver une seule utilisation qui était autre chose qu'une mise en œuvre. Nous avons également échoué à trouver toute utilisation dans le 'monde réel' qui n'est autre chose que de le mettre en œuvre (dans le ~de 60 000 applications que nous avons accès à l').
Maintenant, si vous souhaitez juste d'appliquer un modèle que vous voulez de votre "clonable" des objets à mettre en œuvre, c'est tout à fait amende à l'utilisation et à aller de l'avant. Vous pouvez également décider exactement ce que le "clonage" signifie pour vous (c'est à dire profonde ou peu profonde). Toutefois, dans ce cas, il n'y a pas besoin de nous (BCL) de la définir. Nous n'définir des abstractions de la BCL quand il y a un besoin d'échanger des instances de taper comme cette abstraction entre les indépendants bibliothèques.
David Kean (BCL Équipe)
ICloneable<out T>
pourrait être très utile si elle a hérité deISelf<out T>
, avec une méthode uniqueSelf
de typeT
. On n'a pas souvent besoin de "quelque chose qui est clonable", mais on peut très bien avoir besoin d'unT
c'est clonable. Si un clonable objet implémenteISelf<itsOwnType>
, une routine qui a besoin d'unT
c'est clonable peut accepter un paramètre de typeICloneable<T>
, même si pas tous de la clonable dérivés deT
partagent un ancêtre commun.ICloneable<T>
pourrait être utile pour cela, mais un cadre plus large pour le maintien parallèle changeant et immuable classes pourraient être plus utiles. En d'autres termes, le code qui a besoin de voir ce que certains type deFoo
contient mais qui n'est ni va muter, ni s'attendre à ce que ça ne changera jamais pu utiliser unIReadableFoo
, tandis que...Foo
pourrait utiliser unImmutableFoo
alors que le code qu'il veut manipuler unMutableFoo
. Le Code donné n'importe quel type deIReadableFoo
devrait être en mesure d'obtenir un mutables ou immuable version. Un tel cadre serait bien, mais malheureusement je ne trouve pas de bonne façon de mettre les choses en place d'une façon générale. Si il y avait un moyen cohérent de faire un wrapper en lecture seule pour une classe, une telle chose pourrait être utilisé en combinaison avecICloneable<T>
pour rendre immuable copie d'une classe qui détientT
'.List<T>
, tels que la clonéList<T>
est une nouvelle collection de portefeuille des pointeurs vers tous les mêmes objets dans la collection d'origine, il y a deux façons de le faire sansICloneable<T>
. La première est laEnumerable.ToList()
méthode d'extension:List<foo> clone = original.ToList();
la seconde est LaList<T>
constructeur qui prend unIEnumerable<T>
:List<foo> clone = new List<foo>(original);
je soupçonne que la méthode d'extension est probablement juste d'appeler le constructeur, mais deux de ces à faire ce que vous demandez. 😉IEnumerable<T>
, on peut produire une séquence immuable en l'appelantToList
et puis habillage dans unReadOnlyCollection<T>
ou autrement s'assurer qu'il ne sera jamais exposé à tout ce qui pourrait les transformer. Mes plaintes qui sont (1) uneList<T>
est juste une sorte de chose; il n'y a pas d'interface standard qui permettent de faire un immuable copie de tout ce qui implémente l'interface sans avoir à connaître ce que c'est; (2) le "clonage" d'une instance qui est déjà immuable par simplement la copie de la référence peut être de plusieurs ordres de grandeur plus efficace...IEnumerable<T> mySequence, and one wants
wasSequence " pour désigner un objet qui retourne toujours la séquence d'éléments quimySequence
retourne maintenant. SimySequence
estEnumerable.Range(1,1000000)
, on pourrait direwasSequence=new ReadOnlyCollection<int>(mySequence.ToList());
mais la copie de la référence pourrait être de plusieurs ordres de grandeur plus rapide.ReadOnlyCollection<T>
ne peut être construit à partir d'unIList<T>
, de sorte que si vous voulez utiliser un autre immuable collection wrapper, vous êtes un peu coincé avec le convertir en unList<T>
(ou un tableau) en premier. Je suis d'accord qu'il ne serait pas logique de faire un clone immuable de la collection, mais encore une fois, je ne saisReadOnlyCollection<T>
, de sorte qu'il serait trivial de prévoir une exclusion explicite pour ce cas. Comme pourEnumerable.Range()
, j'ai eu à le regarder pour voir ce que c'est, mais...IEnumerable<int>
, donc il n'y a aucun moyen de savoir si c'est modifiable ou non, sans essayer de le convertir en uneIList<int>
ou quelque chose comme ça et essayer de le changer. La seule chose que vous pouvez faire dans cette situation est d'énumérer dans une liste ou d'une matrice, et de créer unReadOnlyCollection<int>
à partir de, si vous êtes désireux de vous assurer que vous avez un immuable de la collection.SynchronizedReadOnlyCollection<T>
,ReadOnlyObservableCollection<T>
, etReadOnlyDictionary<TKey,TValue>
. Et il ressemble à la BCL est de travailler sur l'ajout de plus (j'espère qu'ils vont tout mettre en œuvreIReadOnlyCollection<T>
à taper facilement vérifier). J'ai seulement utiliséReadOnlyCollection<T>
à une occasion, donc je n'ai pas la prétention d'être un expert dans ce domaine. Et encore, je suis pas en train de débattre de vous, j'étais juste en pointant une solution simple pour le scénario, vous l'avez souligné. 😉Enumerable.Range
, btw, est immuable de l'objet qui encapsule la séquence {1, 2, 3, 4,..., 9999999, 10000000}. Compte tenu de cette séquence, on pourrait faire un immuable objet qui encapsule la même séquence par la construction de 10 000 000-élément du tableau, mais simplement la copie de la référence est un beaucoup moins cher.IConvertibleToImmutable
avec unAsImmutable
, puis avec des types immuables ou partiellement immuable la sauvegarde de magasins pourraient le mettre en œuvre de manière plus efficace qu'un straight-up clone.IReadOnlyCollection<T>
échoue à cet égard puisqu'elle est mise en œuvre surList<T>
! [facepalm] peu importe, tout ce que je suggère est un moyen de cloner une liste, sans même parler de la mutabilité. Merci pour le débat bien. 😉Je pense que la question "pourquoi" est inutile. Il y a beaucoup d'interfaces/classes/etc... ce qui est très utile, mais n'est pas partie .NET Frameworku bibliothèque de base.
Mais, surtout, vous pouvez le faire vous-même.
Il est assez facile de écrire l'interface vous-même si vous en avez besoin:
Avoir lu récemment l'article Pourquoi la Copie d'un Objet est une chose terrible à faire?, je pense que cette question a besoin d'une plus clafirication. D'autres réponses ici de fournir de bons conseils, mais encore la réponse n'est pas complète - pourquoi pas
ICloneable<T>
?Utilisation
Donc, vous avez une classe qui l'implémente. Alors qu'auparavant vous aviez une méthode qui voulait
ICloneable
, il doit désormais être générique à accepterICloneable<T>
. Vous auriez besoin de les modifier.Ensuite, vous pourriez avoir obtenu une méthode qui vérifie si un objet
is ICloneable
. Que faire maintenant? Vous ne pouvez pas faireis ICloneable<>
et que vous ne connaissez pas le type de l'objet lors de la compilation de type, vous ne pouvez pas faire la méthode générique. Premier vrai problème.Si vous avez besoin d'avoir à la fois
ICloneable<T>
etICloneable
, l'ancien œuvre de ces dernières. Ainsi, un opérateur devra mettre en œuvre les deux méthodes -object Clone()
etT Clone()
. Non, merci, nous avons déjà assez de plaisir avecIEnumerable
.Comme l'a déjà souligné, il y a aussi la complexité de l'héritage. Alors que la covariance peut sembler pour résoudre ce problème, un type dérivé doit mettre en œuvre
ICloneable<T>
de son propre type, mais il y a déjà une méthode avec la même signature (= les paramètres, essentiellement) - leClone()
de la classe de base. Faire de votre nouveau clone de l'interface de la méthode explicite est inutile, vous perdrez l'avantage d'avoir demandé lors de la création deICloneable<T>
. Ajoutez lanew
mot-clé. Mais n'oubliez pas que vous aussi, vous avez besoin pour remplacer la classe de baseClone()
(la mise en œuvre doit rester uniforme pour toutes les classes dérivées, c'est à dire le retour de l'objet même de chaque méthode clone, de sorte que la base de méthode clone doit êtrevirtual
)! Mais, malheureusement, vous ne pouvez pas à la foisoverride
etnew
méthodes avec la même signature. Le choix du premier mot-clé, vous perdez l'objectif que vous avez voulu avoir lors de l'ajout d'ICloneable<T>
. Sélections d'angle le second, vous vous souhaitez rompre l'interface elle-même, faisant les méthodes que doit faire la même déclaration des objets différents.Point
Vous voulez
ICloneable<T>
pour le confort, mais le confort n'est pas ce que les interfaces sont conçues pour, leur signification est (en général POO) pour unifier le comportement des objets (bien qu'en C#, il est limité à unifier le comportement extérieur, par exemple, les méthodes et les propriétés, pas de leur fonctionnement).Si la première raison n'a pas convaincu vous encore, vous pourriez objecter que
ICloneable<T>
pourrait également travailler de manière restrictive, afin de limiter le type retourné par la méthode clone. Cependant, les mauvais programmeur peut mettre en œuvreICloneable<T>
où T n'est pas le type qui est mise en œuvre. De sorte que, pour atteindre votre restriction, vous pouvez ajouter une belle contrainte pour le paramètre générique:public interface ICloneable<T> : ICloneable where T : ICloneable<T>
Certainement plus restrictive que l'un sans
where
, vous ne pouvez toujours pas limiter cette T est le type qui implémente l'interface (que vous pouvez tirer d'ICloneable<T>
de type différent qui l'implémente).Vous voyez, même ce but ne pouvait être atteint (l'original
ICloneable
échoue également à présent, aucune interface ne peut vraiment limiter le comportement de la mise en œuvre de la classe).Comme vous pouvez le voir, cela prouve faisant l'interface générique est à la fois difficile à mettre pleinement en œuvre et aussi vraiment inutiles et inutiles.
Mais pour en revenir à la question, vraiment ce que vous cherchez est pour avoir le confort lors du clonage d'un objet. Il y a deux façons de le faire:
D'autres méthodes
Cette solution fournit à la fois le confort et l'intention de comportement des utilisateurs, mais c'est aussi trop longue à mettre en place. Si nous ne nous en voulez pas "à l'aise" méthode retournant le type de courant, il est beaucoup plus facile d'avoir juste
public virtual object Clone()
.Donc, nous allons voir le "nec plus ultra" de la solution - ce qu'en C# est vraiment destiné à nous donner du réconfort?
Les méthodes d'Extension!
Il est nommé Copie ne pas entrer en collision avec le courant Clone méthodes (compilateur préfère le type propre a déclaré méthodes sur l'extension de celles). Le
class
contrainte est-il pour la vitesse (ne nécessite pas de nulle vérifier etc.).J'espère que cela clarifie la raison pourquoi ne pas faire une
ICloneable<T>
. Toutefois, il est recommandé de ne pas mettre en œuvreICloneable
à tous.ICloneable
est pour les types de valeur, d'où il pourrait contourner la boxe de la méthode Clone, et il implique que vous avez de la valeur "unboxed". Et que les structures peuvent être clonés (faiblement), il n'est pas nécessaire de le mettre en œuvre (à moins de faire que cela signifie copie en profondeur).Bien que la question est très vieux (5 ans à partir de l'écriture de cet réponses 🙂 et a déjà été répondu, mais j'ai trouvé cet article répond à la question très bien, vérifier ici
EDIT:
Voici la citation de l'article qui répond à la question (assurez-vous de lire l'article au complet, il inclut d'autres choses intéressantes):
Un gros problème, c'est qu'ils pourraient ne pas restreindre T à la même classe. Par exemple ce qui vous empêcherait de le faire:
Ils ont besoin d'un paramètre de restriction comme:
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
pourrait contraindreT
pour correspondre à son propre type, ce qui n'aurait pas la force de mise en œuvre deClone()
de retourner quelque chose à distance ressemblant à l'objet sur lequel il a été cloné. De plus, je dirais que si l'on est en utilisant l'interface de covariance, il peut être préférable d'avoir des classes qui implémententICloneable
être scellé, l'interfaceICloneable<out T>
comprennent unSelf
la propriété, qui est prévue pour retourner lui-même, et...ICloneable<BaseType>
ouICloneable<ICloneable<BaseType>>
. LeBaseType
en question devrait avoir unprotected
méthode de clonage, qui sera appelé par le type qui implémenteICloneable
. Ce serait la possibilité que l'on pourrait le souhaiter avoir unContainer
, unCloneableContainer
, unFancyContainer
, et unCloneableFancyContainer
, le dernier étant utilisable dans le code qui nécessite une clonable dérivés deContainer
ou qui nécessite uneFancyContainer
(mais ne se soucie pas si c'est clonable).FancyList
type qui pourrait être cloné sensiblement, mais un dérivé peut automatiquement persistent son état dans un fichier de disque (spécifié dans le constructeur). Le type dérivé ne pouvait pas être cloné, parce que son état serait attaché à une mutable singleton (le fichier), mais cela ne devrait pas empêcher l'utilisation du type dérivé dans les endroits qui ont besoin de la plupart des fonctionnalités d'unFancyList
mais n'aurait pas besoin de le cloner.C'est une très bonne question... Vous pourriez faire votre propre, cependant:
Andrey dit que c'est considéré comme un mauvais API, mais je n'ai rien entendu à propos de cette interface de devenir obsolète. Et ce serait casser des tonnes d'interfaces...
La méthode Clone doit effectuer une copie superficielle.
Si l'objet fournit également copie en profondeur, une surcharge du Clone ( bool profonde ) peut être utilisé.
EDIT: le Modèle que j'utilise pour le "clonage "" un objet, c'est le passage d'un prototype dans le constructeur.
Ce qui supprime toute possibilité de code redondant de mise en œuvre de situations.
BTW, parler des limitations de ICloneable, n'est-il pas vraiment à l'objet lui-même de décider si un clone simple ou profond clone, ou même une partie peu profonde/partie profonde clone, doivent-ils être effectués? Doit-on vraiment s'en soucier, tant que l'objet fonctionne comme prévu? Dans certaines occasions, un bon Clone de la mise en œuvre pourrait très bien inclure à la fois superficiels et profonds de clonage.