logotipo

Implements ICloneable



Descripción general

Cuáles son los pasos a seguir para copiar objetos. Como se realiza una copia total de un objeto, Una clonación

[TOC] Tabla de Contenidos


↑↑↑

Una clase a modo de ejemplo:

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


↑↑↑

Las famosas referencias a objetos

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

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


↑↑↑

El método Clone

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


↑↑↑

El constructor copia

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 interfaz ICloneable

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)


↑↑↑

La interfaz ICloneable, con Tipos

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

  1. Definir el método ICloneable.Clone de la interfaz privado.
  2. Definir un método Clone público que devuelve un tipo "Parrafo" y hacer que el método ICloneable.Clone apunte al mismo.
'''-------------------------------------------------------------
''' <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


↑↑↑

Las copias profundas de objetos

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


↑↑↑

Código Implementación Interfaz ICloneable

#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


↑↑↑

Más información en:


↑↑↑

A.2.Enlaces

[Para saber mas]
[Grupo de documentos]
[Documento Index]
[Documento Start]
[Imprimir el Documento]
© 1.997- 2.008 - La Güeb de Joaquín
Joaquin Medina Serrano
Ésta página es española