¿Por qué no ICloneable ?

¿Hay alguna razón particular por la cual no existe un ICloneable genérico?

Sería mucho más cómodo, si no tuviera que lanzarlo cada vez que clono algo.

    ICloneable ahora se considera una API incorrecta, ya que no especifica si el resultado es una copia profunda o poco profunda. Creo que es por eso que no mejoran esta interfaz.

    Probablemente pueda hacer un método de extensión de clonación tipeado, pero creo que requeriría un nombre diferente ya que los métodos de extensión tienen menos prioridad que los originales.

    Además de la respuesta de Andrey (con la que estoy de acuerdo, +1): cuando se hace ICloneable , también puede elegir la implementación explícita para hacer que el público Clone() devuelva un objeto tipeado:

     public Foo Clone() { /* your code */ } object ICloneable.Clone() {return Clone();} 

    Por supuesto, hay un segundo problema con un genérico ICloneable – herencia.

    Si tengo:

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

    ¿Y implementé ICloneable , entonces implemento ICloneable ? ICloneable ? Rápidamente comienzas a implementar muchas interfaces idénticas … Comparar con un elenco … ¿y realmente es tan malo?

    Necesito preguntar, ¿qué harías exactamente con la interfaz aparte de implementarla? Las interfaces normalmente solo son útiles cuando lo lanzas (es decir, esta clase admite ‘IBar’), o tienen parámetros o setters que lo toman (es decir, tomo un ‘IBar’). Con ICloneable, revisamos todo el Framework y no encontramos un solo uso en ningún lugar que fuera algo más que una implementación del mismo. Tampoco hemos encontrado ningún uso en el “mundo real” que también haga algo más que implementarlo (en las ~ 60,000 aplicaciones a las que tenemos acceso).

    Ahora bien, si desea implementar un patrón que desea que implementen sus objetos ‘clonables’, eso es un uso completamente bueno, y siga adelante. También puede decidir exactamente qué significa “clonación” para usted (es decir, profundo o superficial). Sin embargo, en ese caso, no es necesario que nosotros (el BCL) lo definamos. Solo definimos abstracciones en el BCL cuando existe la necesidad de intercambiar instancias tipadas como esa abstracción entre bibliotecas no relacionadas.

    David Kean (Equipo BCL)

    Creo que la pregunta “por qué” es innecesaria. Hay muchas interfaces / clases / etc … que es muy útil, pero no es parte de la biblioteca base .NET Frameworku.

    Pero, principalmente, puedes hacerlo tú mismo.

     public interface ICloneable : ICloneable { new T Clone(); } public abstract class CloneableBase : ICloneable where T : CloneableBase { public abstract T Clone(); object ICloneable.Clone() { return this.Clone(); } } public abstract class CloneableExBase : CloneableBase where T : CloneableExBase { 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 : CloneableExBase where T : PersonBase { public string Name { get; set; } protected override void FillClone( T clone ) { clone.Name = this.Name; } } public sealed class Person : PersonBase { protected override Person CreateClone() { return new Person(); } } public abstract class EmployeeBase : PersonBase where T : EmployeeBase { public string Department { get; set; } protected override void FillClone( T clone ) { base.FillClone( clone ); clone.Department = this.Department; } } public sealed class Employee : EmployeeBase { protected override Employee CreateClone() { return new Employee(); } } 

    Es muy fácil escribir la interfaz usted mismo si la necesita:

     public interface ICloneable : ICloneable where T : ICloneable { new T Clone(); } 

    Después de leer recientemente el artículo ¿Por qué copiar un objeto es algo terrible que hacer? , Creo que esta pregunta necesita una confirmación adicional. Otras respuestas aquí proporcionan buenos consejos, pero aún así la respuesta no está completa: ¿por qué no ICloneable ?

    1. Uso

      Entonces, tienes una clase que lo implementa. Si bien anteriormente tenía un método que quería ICloneable , ahora tiene que ser genérico para aceptar ICloneable . Deberías editarlo.

      Entonces, podría haber obtenido un método que verifica si un objeto is ICloneable . ¿Ahora que? No puede hacer is ICloneable<> y como no conoce el tipo de objeto en comstackción, no puede hacer que el método sea genérico. Primer problema real

      Por lo tanto, debe tener ambos ICloneable e ICloneable , el primero implementando el último. Por lo tanto, un implementador debería implementar ambos métodos: object Clone() y T Clone() . No, gracias, ya tenemos suficiente diversión con IEnumerable .

      Como ya se señaló, también existe la complejidad de la herencia. Mientras que la covarianza puede parecer que resuelve este problema, un tipo derivado necesita implementar ICloneable de su propio tipo, pero ya hay un método con la misma firma (= parámetros, básicamente) – el Clone() de la clase base. Hacer explícita su nueva interfaz de método de clonación no tiene sentido, perderá la ventaja que buscaba al crear ICloneable . Así que agrega la new palabra clave. Pero no olvide que también deberá anular la clase base ‘ Clone() (la implementación debe permanecer uniforme para todas las clases derivadas, es decir, devolver el mismo objeto de cada método de clonación, por lo que el método de clonación base debe ser virtual )! Pero, desafortunadamente, no se pueden override ni los métodos new con la misma firma. Al elegir la primera palabra clave, perdería el objective que deseaba tener al agregar ICloneable . Escogiendo el segundo, romperías la interfaz, haciendo que los métodos devuelvan diferentes objetos.

    2. Punto

      Desea ICloneable para la comodidad, pero la comodidad no es para lo que están diseñadas las interfaces, su significado es (en general OOP) para unificar el comportamiento de los objetos (aunque en C #, se limita a unificar el comportamiento externo, por ejemplo, los métodos y propiedades, no su funcionamiento).

      Si el primer motivo aún no lo ha convencido, podría objetar que ICloneable también podría funcionar de forma restrictiva, para limitar el tipo devuelto por el método de clonación. Sin embargo, desagradable progtwigdor puede implementar ICloneable donde T no es el tipo que lo está implementando. Entonces, para lograr su restricción, puede agregar una buena restricción al parámetro genérico:
      public interface ICloneable : ICloneable where T : ICloneable
      Ciertamente más restrictivo que el que no tiene where , aún no puede restringir que T sea ​​el tipo que está implementando la interfaz (puede derivarse de ICloneable de diferente tipo que lo implemente).

      Usted ve, incluso este propósito no se pudo lograr (el ICloneable original también falla en esto, ninguna interfaz puede limitar realmente el comportamiento de la clase de implementación).

    Como puede ver, esto demuestra que la interfaz genérica es difícil de implementar y también innecesaria e inútil.

    Pero volviendo a la pregunta, lo que realmente buscas es tener consuelo al clonar un objeto. Hay dos maneras de hacerlo:

    Métodos adicionales

     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(); } } 

    Esta solución brinda comodidad y comportamiento intencionado a los usuarios, pero también es demasiado larga para implementarla. Si no queremos que el método “cómodo” devuelva el tipo actual, es mucho más fácil tener solo public virtual object Clone() .

    Entonces, veamos la solución “definitiva”: ¿qué en C # realmente está destinado a brindarnos comodidad?

    Métodos de extensión!

     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(this T obj) where T : class, ICloneable { return obj.Clone() as T; } 

    Se llama Copy para que no colisione con los métodos de clonación actuales (el comstackdor prefiere los métodos declarados del tipo sobre los de extensión). La restricción de class está ahí para la velocidad (no requiere verificación nula, etc.).

    Espero que esto aclare la razón por la cual no se hace ICloneable . Sin embargo, se recomienda no implementar ICloneable en absoluto.

    Aunque la pregunta es muy antigua (5 años después de escribir estas respuestas 🙂 y ya fue respondida, pero encontré que este artículo responde la pregunta bastante bien, verifíquelo aquí

    EDITAR:

    Aquí está la cita del artículo que responde a la pregunta (asegúrese de leer el artículo completo, incluye otras cosas interesantes):

    Hay muchas referencias en Internet que apuntan a una publicación de blog de 2003 de Brad Abrams, en ese momento empleada en Microsoft, en la que se discuten algunas ideas sobre ICloneable. La entrada del blog se puede encontrar en esta dirección: Implementando ICloneable . A pesar del título engañoso, esta entrada de blog llama a no implementar ICloneable, principalmente debido a la confusión superficial / profunda. El artículo termina en una sugerencia directa: si necesita un mecanismo de clonación, defina su propia metodología Clonar o Copiar, y asegúrese de documentar claramente si se trata de una copia profunda o superficial. Un patrón apropiado es: public Copy();

    Un gran problema es que no pudieron restringir T para que fuera la misma clase. Por ejemplo, lo que te impediría hacer esto:

     interface IClonable { T Clone(); } class Dog : IClonable { //not what you would expect, but possible JackRabbit Clone() { return new JackRabbit(); } } 

    Necesitan una restricción de parámetros como:

     interfact IClonable where T : implementing_type 

    Es una muy buena pregunta … Aunque podrías hacerte la tuya propia:

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

    Andrey dice que se considera una API mala, pero no he oído nada acerca de que esta interfaz se haya quedado obsoleta. Y eso rompería toneladas de interfaces … El método Clone debería realizar una copia poco profunda. Si el objeto también proporciona una copia profunda, se puede usar un clon sobrecargado (bool deep).

    EDITAR: Patrón que uso para “clonar” un objeto, está pasando un prototipo en el constructor.

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

    Esto elimina cualquier posible situación de implementación de código redundante. Por cierto, hablando de las limitaciones de ICloneable, ¿no le corresponde al objeto decidir si se debe realizar un clon superficial o un clon profundo, o incluso un clon parcial o parcialmente profundo? ¿Deberíamos importarnos realmente, siempre y cuando el objeto funcione según lo previsto? En algunas ocasiones, una buena implementación de Clones podría incluir tanto la clonación superficial como la profunda.