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.
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.
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.
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 CloneableFoo
hérite de [non clonable publiquement] Foo
, devrait CloneableDerivedFoo
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é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é.
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 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?
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)
ICloneable<out T>
pourrait être très utile s'il hérité de ISelf<out T>
, avec une seule méthode Self
de 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 T
qui est clonable. Si un objet clonable est implémenté ISelf<itsOwnType>
, une routine qui a besoin d'un T
cloneable peut accepter un paramètre de type ICloneable<T>
, même si tous les dérivés clonables ne T
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 que Foo
contient 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 ...
Foo
pourrait utiliser un ImmutableFoo
code while qui voudrait le manipuler pourrait utiliser un MutableFoo
. 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 avec ICloneable<T>
pour faire une copie immuable d'une classe qui contient T
'.
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. ;)
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(); }
}
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();
}
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é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 new
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'être virtual
)! 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'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.
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:
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?
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 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 mettre ICloneable
en œuvre du tout.
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):
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();
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
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
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 Self
propriété qui devrait se retourner, et ...
ICloneable<BaseType>
ou ICloneable<ICloneable<BaseType>>
. La personne BaseType
en question devrait avoir une protected
mé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 FancyContainer
et un CloneableFancyContainer
, ce dernier étant utilisable dans du code qui nécessite un dérivé clonable de Container
ou qui nécessite un FancyContainer
(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'un FancyList
mais pas pour le cloner.
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.