Cuáles son los pasos a seguir para copiar objetos. Como se realiza una copia total de un objeto, Una clonación
Imports System.Drawing ' para el Color ' ------------------------------------------------- ' Si aparece un error en la linea anterior tienes que ' establecer en el proyecto una referencia a System.Drawing ' ------------------------------------------------- ''' <summary> ''' Clase Parrafo, Ejemplo para mostrar como hacer clonaciones ''' </summary> Public Class Parrafo Implements ICloneable Public TextoDelParrafo As System.String = String.Empty Public Fuente As System.Drawing.Font = Nothing '---------------------------------------------------------------- ' El campo [Fuente] es un campo que contiene un objeto que, ' a su vez, está compuesto de otros objetos internamente ' ' La idea de este ejemplo es ilustrar la clonación de objetos y ' la problemática que existe cuando se clonan objetos que ' contienen objetos que a su vez están compuestos de más objetos. ' ' Uno de los objetos de este tipo, que todo el mundo conoce, y ' disponible en la biblioteca de .NET es la fuente las letras [Font]. ' ---------------------------------------------------------------- Public Sub New() ' o hacer nada End Sub Public Sub New(ByVal nombre As String, ByVal fuente As Font) Me.TextoDelParrafo = nombre Me.Fuente = fuente End Sub End Class
En .NET todo son objetos, pero existen dos tipos de objetos: los tipos por valor y los tipos por referencia.
Una referencia a un objeto, (Un puntero según C++) permite acceder a la dirección de memoria donde se encuentra el objeto y utilizar todos sus métodos y propiedades.
'''----------------------------------------------- ''' <summary> ''' Obtener La Referencia del objeto ''' </summary> ''' <value>Un Objeto</value> ''' <returns> ''' El puntero a esta clase. Devuelve Una referencia a este objeto ''' para poder acceder a traves de ella a todos lo metodos de la clase ''' </returns> Public ReadOnly Property ObtenerLaReferencia() As Object Get Return Me End Get End Property
Hay que tener en cuenta que cuando se asigna una variable a otra,
Objeto2 = Objeto1
Lo que ocurre es lo siguiente:
En resumen, solo se puede usar la asignación cuando se trata de tipos de valor (en general números). Para el resto de objetos deberemos emplear las técnicas especiales de copias de objetos, llamadas técnicas de clonación de objetos.
El método MemberwiseClone [VER mas información] crea una copia superficial mediante la creación de un nuevo objeto y la copia posterior de los campos no estáticos del objeto actual en el objeto nuevo.
Si un campo es un tipo de valor, se realiza una copia bit a bit del campo.
Si un campo es un tipo de referencia, se copia la referencia pero no el objeto al que se hace referencia; por consiguiente, el objeto original y su copia hacen referencia al mismo objeto.
El problema que tiene es que es un método protegido heredado de System.Object, su firma es la siguiente
Protected Function MemberwiseClone As System.Object
Es decir, que solo lo ven las clases derivadas (o clases hijas) de la clase actual, con lo que aunque es un método que cumple con su trabajo, no siempre podremos emplearlo.
Si queremos implementarlo en nuestra clase tendremos que escribir algo así
Public Overloads Function MemberwiseClone() As System.Object Return MyBase.MemberwiseClone End Function
Lo mejor para realizar una clonación es escribir un método que realice la copia del objeto, a este método le llamaremos Clone. En él, se copian una a una las variables miembro de la clase
''' <summary> ''' Obtener una copia completa del objeto ''' </summary> ''' <returns>La referencia a un objeto del mismo tipo que este</returns> ''' <remarks> ''' <para> <c>Entradas ...:</c> ''' Se utilizan las variables miembro del objeto </para> ''' <para> <c>Salidas ....:</c> ''' Los valores de las variables miembro no se modifican </para> ''' <para> <c>Observacion :</c> ''' Se crea un objeto nuevo (diferente) con todos los ''' valores de las variables miembro del objeto viejo copiados ''' en el objeto nuevo, es un duplicado perfecto ''' </para> '''</remarks> Public Function Clone1() As Parrafo ' definir el objeto Dim objNuevo As Parrafo = Nothing objNuevo = New Parrafo ' cargar los valores de las variables miembro ' en el objeto nuevo objNuevo.TextoDelParrafo = Me.TextoDelParrafo ' en este caso funciona pero no siempre es asi objNuevo.Fuente = Me.Fuente ' devolver la referencia del objeto creado Return objNuevo End Function
Si existieran objetos dentro de la clase podemos copiarlos utilizando el método Clone de los mismos. En el código que se muestra a continuación se indica la forma de clonar un objeto utilizando [MemberwiseClone] y la clonación interna de los objetos hijos
Public Function Clone2() As Parrafo ' definir el objeto Dim objNuevo As Parrafo = Nothing '----------------------------------------------- ' copiar los valores de las variables ' utilizando la copia superficial objNuevo = CType(MyBase.MemberwiseClone, Parrafo) '----------------------------------------------- ' Ahora copio los objetos compuestos '----------------------------------------------- ' [System.Drawing.Font] ' es un objeto compuesto de objetos ' no debo emplear la asignacion (aunque funcione) ' Me.Fuente = objNuevo.Fuente ' porque quiero OTRO OBJETO y no tener la misma referencia ' en su lugar usar CLONE ' Copiar un Objeto Hijo utilizando su método clone objNuevo.Fuente = CType(Me.Fuente.Clone, System.Drawing.Font) ' devolver la referencia del objeto creado Return objNuevo End Function
La forma más sencilla de copiar un objeto en otro es escribir un Constructor Copia. Observa el código que te muestro a continuación. La idea es copiar en los campos de la clase, los valores de un objeto que se recibe por parámetro
''' <summary>Constructor Copia </summary> Public Sub New(ByVal objNuevo As Parrafo) '---------------------------------------------------- ' copiar los valores de las variables miembro ' del 'objeto nuevo' recibido por parametro ' en las variables de la clase Me.TextoDelParrafo = objNuevo.TextoDelParrafo ' es un objeto compuesto de objetos ' no debo emplear la asignacion (aunque funcione) ' Me.Fuente = objNuevo.Fuente ' en su lugar usar CLONE ' Copiar un Objeto Hijo utilizando su método clone If Not (objNuevo Is Nothing) Then Me.Fuente = CType(objNuevo.Fuente.Clone, Font) Else Me.Fuente = Nothing End If End Sub
Observa que la idea es la misma que la del método Clone, pero funciona al revés. Clone devuelve un objeto con los datos copiados, y un constructor Copia, lo que hace es copiar en este objeto los datos de otro objeto
Existe una variante que a mí me gusta más y es la que normalmente empleo cuando uso este constructor y que consiste en lo siguiente:
''' <summary>Constructor Copia </summary> Public Sub New(ByVal objNuevo As Parrafo) '---------------------------------------------------- ' copiar los valores de las variables miembro Call FormVO(objNuevo) End Sub
'''----------------------------------------------- ''' <summary> ''' Copia los valores del objeto en las variables de la clase ''' </summary> ''' <param name="objNuevo"> ''' Es el objeto del que se copian los datos</param> ''' <remarks> ''' <para> <c>Variables miembro.:</c> </para> ''' <para> Las variables miembro del objeto recibido por parametro solo se leen</para> ''' <para> Los valores de las variables miembro de esta clase se modifican </para> ''' ''' <para> <c>Observaciones :</c> </para> ''' <para>Se copian los valores de las variables miembro del 'objeto nuevo' ''' recibido por parametro en las variables de la clase ''' </para> ''' </remarks> Public Sub FormVO(ByVal objNuevo As Parrafo) If Not (objNuevo Is Nothing) Then '---------------------------------------------------- ' copiar los valores de las variables miembro ' del 'objeto nuevo' recibido por parametro ' en las variables de la clase '---------------------------------------------------- Me.TextoDelParrafo = objNuevo.TextoDelParrafo '----------------------------------------------- ' [System.Drawing.Font] ' es un objeto compuesto de objetos ' no debo emplear la asignacion (aunque funcione) ' Me.Fuente = objNuevo.Fuente ' porque quiero OTRO OBJETO y no tener la misma referencia ' en su lugar usar CLONE ' Copiar un Objeto Hijo utilizando su método clone Me.Fuente = CType(objNuevo.Fuente.Clone, System.Drawing.Font) End If End Sub
La verdad es que ya que nos molestamos en escribir el método Clone, no nos cuesta nada implementar la interfaz ICloneable. Microsoft ha dispuesto la interfaz ICloneable para poder implementar el método Clone con el que se realizan copias del objeto
A estas alturas supongo que no hay que indicar como se implementa la interfaz pero solo por si acaso
Public Class Parrafo Implements ICloneable . . . . . . End Class
Un ejemplo de un método Clone Implementando la interfaz ICloneable
Public Function Clone() As System.ObjectImplements System.ICloneable.Clone ' definir el objeto Dim objNuevo As Parrafo = Nothing objNuevo = New Parrafo ' cargar los valores de las variables miembro ' en el objeto nuevo objNuevo.TextoDelParrafo = Me.TextoDelParrafo '----------------------------------------------- ' Ahora copio los objetos compuestos '----------------------------------------------- ' [System.Drawing.Font] ' es un objeto compuesto de objetos ' no debo emplear la asignacion (aunque funcione) ' Me.Fuente = objNuevo.Fuente ' porque quiero OTRO OBJETO y no tener la misma referencia ' en su lugar usar CLONE ' Copiar un Objeto Hijo utilizando su método clone objNuevo.Fuente = CType(Me.Fuente.Clone, System.Drawing.Font) ' devolver la referencia del objeto creado Return objNuevo End Function
La interfaz ICloneable solo tienen un método Clone y además devuelve un tipo System.Object. Este hecho implica la necesidad de utilizar la instrucción Ctype para realizar una conversión de tipos y asignar el objeto clonado a una variable cuyo tipo haya sido declarada previamente. [VER mas información]
parrafoClonado = CType(parrafoVerde.Clone, Parrafo)
Hasta ahora todo funciona como estaba previsto, pero lo que realmente quiero, es implementar la interfaz ICloneable y además, que la función Clone me devuelva un objeto de tipo "parrafo" en lugar del objeto de tipo "System.Object" definido en la interfaz
Para resolverlo recurrimos a una "trampa" bastante curiosa pero que funciona perfectamente y es el siguiente
'''------------------------------------------------------------- ''' <summary> ''' Unico método de la interfaz ICloneable (OJO ES PRIVADO) ''' </summary> Private Function ClonaMe() As System.Object Implements ICloneable.Clone Return Me.Clone ' Apuntar al método Clone End Function
'''------------------------------------------------------------- ''' <summary> ''' Obtener una copia completa del objeto ''' </summary> Public Function Clone() As Parrafo ' definir el objeto Dim objNuevo As Parrafo = Nothing objNuevo = New Parrafo ' Copiar los valores de las variables miembro ' en el objeto nuevo objNuevo.TextoDelParrafo = Me.TextoDelParrafo '----------------------------------------------- ' Ahora copio los objetos compuestos '----------------------------------------------- ' [System.Drawing.Font] ' Copiar un Objeto Hijo utilizando su método clone objNuevo.Fuente = CType(Me.Fuente.Clone, System.Drawing.Font) ' devolver la referencia del objeto creado Return objNuevo End Function
Existe otro problema del que hemos hablado de pasada y es el tema de las clonaciones profundas y las clonaciones superficiales
Por ejemplo: Un objeto denominado X, hace referencia a los objetos A y B. El objeto B, a su vez, hace referencia al objeto C.
Escribir una función que realice este trabajo y que funcione sin problemas, es bastante complicado, porque aparte de la profundidad del gráfico nos enfrentamos con el problema de las referencias circulares entre objetos lo que puede originar desbordamientos de la pila -Stak- y por lo tanto un error en la ejecución del programa. La solución a este problema es utilizar técnicas de serialización
A continuación te muestro una función genérica que realiza una clonación profunda de cualquier objeto
'''------------------------------------------------------------- ''' <summary>Crea una copia Profunda del objeto </summary> ''' <typeparam name="T">El Objeto que se va a clonar</typeparam> ''' <returns>Un nuevo objeto con los valores copiados</returns> Private Shared Function ClonacionProfunda (Of T)(ByVal objAClonar As T) As T 'Puedes ver el código de esta función mas adelante, en este mismo artículo. End Function
#Region "Implementación Interfaz ICloneable" '''----------------------------------------------- ''' <summary> ''' Constructor Copia ''' </summary> Public Sub New(ByVal objNuevo As Parrafo) ' copiar los valores de las variables miembro Call FormVO(objNuevo) End Sub '''----------------------------------------------- ''' <summary> ''' Copia los valores del objeto en las variables de la clase ''' </summary> Public Sub FormVO(ByVal objNuevo As Parrafo) If Not (objNuevo Is Nothing) Then '---------------------------------------------------- ' copiar los valores de las variables miembro ' del 'objeto nuevo' recibido por parametro ' en las variables de la clase '---------------------------------------------------- Me.TextoDelParrafo = objNuevo.TextoDelParrafo '----------------------------------------------- '----------------------------------------------- ' Ahora copio los objetos compuestos '----------------------------------------------- ' [System.Drawing.Font] ' es un objeto compuesto de objetos ' no debo emplear la asignacion (aunque funcione) ' Me.Fuente = objNuevo.Fuente ' porque quiero OTRO OBJETO y no tener la misa referencia ' en su lugar usar CLONE ' Copiar un Objeto Hijo utilizando su método clone Me.Fuente = CType(objNuevo.Fuente.Clone, System.Drawing.Font) End If End Sub '''----------------------------------------------- ''' <summary> ''' Crea una copia superficial del objeto ''' </summary> Public Overloads Function MemberwiseClone() As System.Object Return MyBase.MemberwiseClone End Function '--------------------------------------------------------------- ' (OJO ES PRIVADO) '''------------------------------------------------------------- ''' <summary> ''' Unico método de la interfaz ICloneable ''' </summary> Private Function ClonaMe() As System.Object Implements ICloneable.Clone Return Me.Clone ' Apuntar al método Clone End Function '--------------------------------------------------------------- ' (OJO ES PUBLICO) '''------------------------------------------------------------- ''' <summary> ''' Obtener una copia completa del objeto ''' </summary> Public Function Clone() As Parrafo ' llamar al metodo generico que realiza la clonacion Return ClonacionProfunda(Me) End Function '--------------------------------------------------------------- ' (OJO ES PRIVADO) '''------------------------------------------------------------- ''' <summary>Crea una copia Profunda del objeto </summary> ''' <typeparam name="T">El Objeto que se va a clonar</typeparam> ''' <returns>Un nuevo objeto con los valores copiados</returns> ''' <remarks> ''' <para> <c>Variables miembro.:</c> </para> ''' <para> Es una función Genérica, Trata al objeto como un todo ''' No utiliza las variables miembro de las clases con las que trabaja, ''' </para> ''' <example> ''' Public Function Clone() As Colega ''' ' llamar al metodo generico que realiza la clonacion ''' Return ClonacionProfunda(Me) ''' End Function ''' </example> ''' <para>Fecha de creacion 2008/09/05</para> ''' <para>Joaquin Medina Serrano</para> ''' <para>mailto:joaquin@medina.name</para> '''</remarks>u Private Shared Function ClonacionProfunda(Of T)(ByVal objAClonar As T) As T Dim ms As System.IO.MemoryStream = Nothing Dim bf As System.Runtime.Serialization.Formatters.Binary.BinaryFormatter = Nothing Try ' crear una secuencia en memoria ms = New System.IO.MemoryStream(2048) bf = New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter( _ Nothing, _ New System.Runtime.Serialization.StreamingContext( _ Runtime.Serialization.StreamingContextStates.Clone)) ' serializar el objeto en la memoria bf.Serialize(ms, objAClonar) ' situar el puntero en el primer byte de la secuencia ms.Seek(0, System.IO.SeekOrigin.Begin) ' deserializar Dim ObjNuevo As T ObjNuevo = CType(bf.Deserialize(ms), T) ' liberar memoria --> En finally ' devolver el nuevo objeto clonado Return ObjNuevo Catch ex As Exception Throw Finally If Not (ms Is Nothing) Then ' cerrar la secuencia MemoryStream ms.Close() ' Comprobar si se puede llamar a la funcion dipose del objeto If TypeOf ms Is IDisposable Then ms.Dispose() End If ' objeto a null ms = Nothing End If If Not (ms Is Nothing) Then ' objeto a null ms = Nothing End If End Try End Function #End Region
© 1.997- 2.008 - La Güeb de Joaquín | |||||
Joaquin Medina Serrano
|
|||||
|