¿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.
¿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.
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.
List<T>
tuviera un método de clonación, esperaría que produjera uno List<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": si CloneableFoo
hereda de [no clonable públicamente] Foo
, debería CloneableDerivedFoo
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 implementar Clone
. ¿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 llamarse ShallowCopy
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úblico Clone()
devuelva un objeto escrito:
public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
Por supuesto, hay un segundo problema con una ICloneable<T>
herencia genérica .
Si tengo:
public class Foo {}
public class Bar : Foo {}
Y lo implementé ICloneable<T>
, ¿entonces lo implemento ICloneable<Foo>
? ICloneable<Bar>
? Empiezas rápidamente a implementar muchas interfaces idénticas ... Compara con un elenco ... ¿y es realmente tan malo?
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)
ICloneable<out T>
podría ser bastante útil si se hereda de ISelf<out T>
, con un único método Self
de tipo T
. Uno no necesita a menudo "algo que se pueda clonar", pero es muy posible que necesite uno T
que se pueda clonar. Si se implementa un objeto clonable ISelf<itsOwnType>
, una rutina que necesita un T
clonable puede aceptar un parámetro de tipo ICloneable<T>
, incluso si no todos los derivados clonables T
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 contenido Foo
contiene pero que no va a mutarlo ni a esperar que nunca cambie podría usar un IReadableFoo
, mientras que ...
Foo
podría usar un ImmutableFoo
código while que quiere manipularlo podría usar un MutableFoo
. El código dado cualquier tipo de IReadableFoo
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 con ICloneable<T>
para hacer una copia inmutable de una clase que contiene T
'.
List<T>
, de modo que el clonado List<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 ellos ICloneable<T>
. El primero es el Enumerable.ToList()
método de extensión: List<foo> clone = original.ToList();
el segundo es el List<T>
constructor que toma un IEnumerable<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.
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(); }
}
Es muy fácil escribir la interfaz usted mismo si la necesita:
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
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 aceptar ICloneable<T>
. Necesitarías editarlo.
Entonces, podría haber obtenido un método que verifica si un objeto is ICloneable
. ¿Ahora que? No puede hacerlo is 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>
y ICloneable
, el primero implementando el segundo. Por lo tanto, un implementador necesitarí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. 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), 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<T>
. Entonces agregue la new
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 ser virtual
)! Pero, desafortunadamente, no puedes ambos override
ynew
métodos con la misma firma. Al elegir la primera palabra clave, perdería el objetivo que deseaba tener al agregar ICloneable<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 implementar ICloneable<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 derivar ICloneable<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:
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 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?
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;
}
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 implementar ICloneable
en absoluto.
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):
Hay muchas referencias en Internet que apuntan a una publicación de blog de 2003 de Brad Abrams, en ese momento empleado en Microsoft, en la que se discuten algunas ideas sobre ICloneable. La entrada del blog se puede encontrar en esta dirección: Implementación de ICloneable . A pesar del título engañoso, esta entrada del 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 de clonación o copia y asegúrese de documentar claramente si se trata de una copia profunda o superficial. Un patrón apropiado es:
public <type> Copy();
Un gran problema es que no podían restringir que T sea la misma clase. Por ejemplo, qué le impediría hacer esto:
interface IClonable<T>
{
T Clone();
}
class Dog : IClonable<JackRabbit>
{
//not what you would expect, but possible
JackRabbit Clone()
{
return new JackRabbit();
}
}
Necesitan una restricción de parámetros como:
interfact IClonable<T> where T : implementing_type
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
pudiera restringir la T
coincidencia con su propio tipo, eso no obligaría a una implementación de Clone()
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 implementan ICloneable
estén selladas, que la interfaz ICloneable<out T>
incluya una Self
propiedad que se espera que regrese, y ...
ICloneable<BaseType>
o ICloneable<ICloneable<BaseType>>
. El BaseType
en cuestión debería tener un protected
método para la clonación, que sería llamado por el tipo que implementa ICloneable
. Este diseño permitiría la posibilidad de que uno desee tener a Container
, a CloneableContainer
, a FancyContainer
y a CloneableFancyContainer
, siendo este último utilizable en un código que requiere una derivada clonable de Container
o que requiere un FancyContainer
(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 un FancyList
pero no necesitarían para clonarlo
Es una muy buena pregunta ... Sin embargo, podrías hacer la tuya propia:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
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.
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 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.