¿Hay alguna razón particular por la que ICloneable<T>
no existe un genérico ?
Sería mucho más cómodo si no tuviera que lanzarlo cada vez que clono algo.
c#
.net
icloneable
Bluenuance
fuente
fuente
Respuestas:
ICloneable se considera una API incorrecta ahora, ya que no especifica si el resultado es una copia profunda o superficial. Creo que es por eso que no mejoran esta interfaz.
Probablemente pueda hacer un método de extensión de clonación escrito, pero creo que requeriría un nombre diferente ya que los métodos de extensión tienen menos prioridad que los originales.
fuente
List<T>
tuviera un método de clonación, esperaría que produjera unoList<T>
cuyos elementos tengan las mismas identidades que los de la lista original, pero esperaría que cualquier estructura de datos internos se duplicara según sea necesario para garantizar que nada de lo que se haga en una lista afecte Las identidades de los elementos almacenados en el otro. ¿Dónde está la ambigüedad? Un problema mayor con la clonación viene con una variación del "problema del diamante": siCloneableFoo
hereda de [no clonable públicamente]Foo
, deberíaCloneableDerivedFoo
derivar de ...identity
de la lista en sí, por ejemplo (en el caso de la lista de listas)? Sin embargo, ignorando eso, su expectativa no es la única idea posible que las personas puedan tener al llamar o implementarClone
. ¿Qué sucede si los autores de la biblioteca que implementan alguna otra lista no siguen sus expectativas? La API debe ser trivialmente inequívoca, no podría decirse inequívoca.Clone
a todas sus partes, no funcionará de manera predecible, dependiendo de si esa parte fue implementada por ti o esa persona a quien le gusta la clonación profunda. su punto sobre los patrones es válido, pero tener IMHO en API no es lo suficientemente claro: debe llamarseShallowCopy
para enfatizar el punto o no proporcionarse en absoluto.Además de la respuesta de Andrey (con la que estoy de acuerdo, +1): cuando
ICloneable
haya terminado, también puede elegir una implementación explícita para que el públicoClone()
devuelva un objeto escrito:Por supuesto, hay un segundo problema con una
ICloneable<T>
herencia genérica .Si tengo:
Y lo implementé
ICloneable<T>
, ¿entonces lo implementoICloneable<Foo>
?ICloneable<Bar>
? Empiezas rápidamente a implementar muchas interfaces idénticas ... Compara con un elenco ... ¿y es realmente tan malo?fuente
Tengo que preguntar, ¿qué haría exactamente con la interfaz además de implementarla? Por lo general, las interfaces solo son útiles cuando se emite (es decir, esta clase admite 'IBar'), o tiene parámetros o establecedores que lo toman (es decir, tomo un 'IBar'). Con ICloneable, revisamos todo el Framework y no pudimos encontrar un solo uso en ningún lugar que fuera algo más que una implementación del mismo. Tampoco hemos podido encontrar 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, si solo desea aplicar un patrón que desea que implementen sus objetos 'clonables', es un uso completamente bueno, y continúe. También puede decidir exactamente qué significa "clonación" para usted (es decir, profundo o poco profundo). 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 escritas como esa abstracción entre bibliotecas no relacionadas.
David Kean (equipo BCL)
fuente
ICloneable<out T>
podría ser bastante útil si se hereda deISelf<out T>
, con un único métodoSelf
de tipoT
. Uno no necesita a menudo "algo que se pueda clonar", pero es muy posible que necesite unoT
que se pueda clonar. Si se implementa un objeto clonableISelf<itsOwnType>
, una rutina que necesita unT
clonable puede aceptar un parámetro de tipoICloneable<T>
, incluso si no todos los derivados clonablesT
comparten un ancestro común.ICloneable<T>
podría ser útil para eso, aunque un marco más amplio para mantener clases mutables e inmutables paralelas podría ser más útil. En otras palabras, el código que necesita ver qué tipo de contenidoFoo
contiene pero que no va a mutarlo ni a esperar que nunca cambie podría usar unIReadableFoo
, mientras que ...Foo
podría usar unImmutableFoo
código while que quiere manipularlo podría usar unMutableFoo
. El código dado cualquier tipo deIReadableFoo
debería poder obtener una versión mutable o inmutable. Tal marco sería bueno, pero desafortunadamente no puedo encontrar ninguna manera agradable de configurar las cosas de manera genérica. Si hubiera una manera consistente de hacer un contenedor de solo lectura para una clase, tal cosa podría usarse en combinación conICloneable<T>
para hacer una copia inmutable de una clase que contieneT
'.List<T>
, de modo que el clonadoList<T>
es una nueva colección que contiene punteros a todos los mismos objetos de la colección original, hay dos formas sencillas de hacerlo sin ellosICloneable<T>
. El primero es elEnumerable.ToList()
método de extensión:List<foo> clone = original.ToList();
el segundo es elList<T>
constructor que toma unIEnumerable<T>
:List<foo> clone = new List<foo>(original);
sospecho que el método de extensión probablemente solo está llamando al constructor, pero ambos harán lo que está solicitando. ;)Creo que la pregunta "por qué" es innecesaria. Hay muchas interfaces / clases / etc ... lo cual es muy útil, pero no es parte de la biblioteca base .NET Frameworku.
Pero, principalmente, puedes hacerlo tú mismo.
fuente
Es muy fácil escribir la interfaz usted mismo si la necesita:
fuente
Habiendo leído recientemente el artículo ¿Por qué copiar un objeto es algo terrible? , 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<T>
?Uso
Entonces, tienes una clase que lo implementa. Mientras que anteriormente tenía un método que quería
ICloneable
, ahora tiene que ser genérico para aceptarICloneable<T>
. Necesitarías editarlo.Entonces, podría haber obtenido un método que verifica si un objeto
is ICloneable
. ¿Ahora que? No puede hacerlois ICloneable<>
y, como no conoce el tipo de objeto en el tipo de compilación, no puede hacer que el método sea genérico. Primer problema real.Por lo tanto, debe tener ambos
ICloneable<T>
yICloneable
, el primero implementando el segundo. Por lo tanto, un implementador necesitaría implementar ambos métodos,object Clone()
yT Clone()
. No, gracias, ya tenemos suficiente diversión conIEnumerable
.Como ya se señaló, también existe la complejidad de la herencia. Si bien puede parecer que la covarianza resuelve este problema, un tipo derivado necesita implementarse
ICloneable<T>
de su propio tipo, pero ya existe un método con la misma firma (= parámetros, básicamente), elClone()
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 crearICloneable<T>
. Entonces agregue lanew
palabra clave. ¡Pero no olvide que también necesitaría anular la clase base 'Clone()
(la implementación debe ser uniforme para todas las clases derivadas, es decir, para devolver el mismo objeto de cada método de clonación, por lo que el método de clonación base debe servirtual
)! Pero, desafortunadamente, no puedes ambosoverride
ynew
métodos con la misma firma. Al elegir la primera palabra clave, perdería el objetivo que deseaba tener al agregarICloneable<T>
. Al elegir el segundo, rompería la interfaz en sí misma, haciendo que los métodos que deberían hacer lo mismo devuelvan diferentes objetos.Punto
Desea
ICloneable<T>
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 la primera razón aún no lo ha convencido, podría objetar que
ICloneable<T>
también podría funcionar de manera restrictiva, para limitar el tipo devuelto por el método de clonación. Sin embargo, el programador desagradable puede implementarICloneable<T>
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<T> : ICloneable where T : ICloneable<T>
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 derivarICloneable<T>
de un tipo diferente que lo implementa).Verá, incluso este propósito no se pudo lograr (el original
ICloneable
también falla en esto, ninguna interfaz puede realmente limitar el comportamiento de la clase implementadora).Como puede ver, esto demuestra que hacer que la interfaz genérica sea difícil de implementar por completo y realmente 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
Esta solución proporciona comodidad y comportamiento previsto a los usuarios, pero también es demasiado larga para implementarla. Si no quisiéramos que el método "cómodo" devolviera el tipo actual, es mucho más fácil tenerlo justo
public virtual object Clone()
.Entonces, veamos la solución "definitiva": ¿qué es lo que en C # tiene realmente la intención de brindarnos comodidad?
¡Métodos de extensión!
Se llama Copiar para no chocar con los métodos de clonación actuales (el compilador prefiere los métodos declarados del tipo sobre los de extensión). La
class
restricción está ahí para la velocidad (no requiere verificación nula, etc.).Espero que esto aclare la razón por la que no hacer
ICloneable<T>
. Sin embargo, se recomienda no implementarICloneable
en absoluto.fuente
ICloneable
es para los tipos de valor, donde podría eludir el boxeo del método Clone, e implica que tiene el valor sin caja. Y como las estructuras se pueden clonar (superficialmente) automáticamente, no hay necesidad de implementarlo (a menos que lo especifique, significa copia profunda).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, compruébelo 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):
fuente
Un gran problema es que no podían restringir que T sea la misma clase. Por ejemplo, qué le impediría hacer esto:
Necesitan una restricción de parámetros como:
fuente
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
pudiera restringir laT
coincidencia con su propio tipo, eso no obligaría a una implementación deClone()
devolver nada remotamente parecido al objeto sobre el que fue clonado. Además, sugeriría que si uno está usando la covarianza de la interfaz, puede ser mejor que las clases que implementanICloneable
estén selladas, que la interfazICloneable<out T>
incluya unaSelf
propiedad que se espera que regrese, y ...ICloneable<BaseType>
oICloneable<ICloneable<BaseType>>
. ElBaseType
en cuestión debería tener unprotected
método para la clonación, que sería llamado por el tipo que implementaICloneable
. Este diseño permitiría la posibilidad de que uno desee tener aContainer
, aCloneableContainer
, aFancyContainer
y aCloneableFancyContainer
, siendo este último utilizable en un código que requiere una derivada clonable deContainer
o que requiere unFancyContainer
(pero no le importa si es clonable).FancyList
tipo que podría clonarse con sensatez, pero una derivada podría persistir automáticamente en un archivo de disco (especificado en el constructor). El tipo derivado no se pudo clonar, porque su estado se uniría al de un singleton mutable (el archivo), pero eso no debería impedir el uso del tipo derivado en lugares que necesitan la mayoría de las características de unFancyList
pero no necesitarían para clonarloEs una muy buena pregunta ... Sin embargo, podrías hacer la tuya propia:
Andrey dice que se considera una mala API, pero no he escuchado nada acerca de que esta interfaz quede obsoleta. Y eso rompería toneladas de interfaces ... El método Clone debería realizar una copia superficial. 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.
Esto elimina cualquier posible situación de implementación de código redundante. Por cierto, hablando de las limitaciones de ICloneable, ¿no depende realmente del objeto mismo decidir si se debe realizar un clon superficial o profundo, o incluso un clon parcialmente superficial / parcialmente profundo? ¿Realmente debería importarnos, siempre y cuando el objeto funcione según lo previsto? En algunas ocasiones, una buena implementación de clonación podría incluir clonación tanto superficial como profunda.
fuente