Curso Básico de Programación
en Visual Basic

 

Entrega Cuarenta y dos: 09/Nov/2002
por Guillermo "guille" Som

Si quieres linkar con las otras entregas, desde el índice lo puedes hacer

 

Buenas, ya estoy de nuevo por aquí... sí, se que han pasado un montón de días... pero...

En esta entrega vamos a seguir con las clases, en particular con las colecciones personalizadas.
Si quieres refrescarte la memoria, podrías volver a las entregas anteriores sobre las clases, según recuerdo son las entregas 37, 38 y 39.

En la entrega 38 se trató el tema de las colecciones, pero de forma genérica, en esta entrega veremos cómo crear nuestra propia clase-colección para que maneje elementos del tipo que nosotros queramos.

Es habitual que los nombres de las colecciones que contendrán elementos de un tipo definido por nosotros, (en este caso de una clase definida por nosotros, no un tipo de datos creado mediante TYPE), tengan un nombre que sea el plural del nombre del objeto que contendrá. Por ejemplo, si la colección va a contener elementos del tipo cColega, se llamaría cColegas. Esto simplemente es una "recomendación", ya que el nombre que tenga la colección es el que tú decidas, al Visual Basic le da igual que se llame cColegas o LosColegas o el nombre que se te venga a la cabeza... siempre y cuando sea un nombre "válido".
Para seguir con la clase definida en la entrega 39, vamos a llamarla cColegas y contendrá elementos del tipo cColega.

Esta colección tendrá los métodos habituales en todas las colecciones, recordémoslos:
Add, para añadir nuevos elementos.
Remove, para eliminar un elemento en cuestión.
Count, para saber cuantos elementos contiene.
Item, para acceder a dicho elemento.
Además se podrá recorrer la colección usando For Each, para ello tendremos que recurrir a un serie de asignaciones algo extrañas, pero... que al fin y al cabo nos permita darle esa funcionalidad a nuestra clase.
También añadiremos otros métodos, tales como:
Clone, para poder hacer copias de la colección, pero no una copia por referencia, sino una copia totalmente independiente de la clase/colección, tal como hicimos con la clase cColega.
Exists, que devolverá un valor Verdadero o Falso, según un elemento esté o no en la colección.
Clear, que eliminará todos los elementos de la colección y la dejará preparada para usarla desde cero.
Además, vamos a añadir un método llamado Equals, que permitirá comprobar si la un objeto del tipo de la colección es igual a otro objeto de ese mismo tipo... esto es útil para poder "comparar" dos objetos. Este mismo método se lo añadiremos a la clase cColega.

Dicho todo esto y antes de ver el código de la colección personalizada, vamos a crear el método Equals de la clase cColega (la cuestión es siempre hacerse de rogar... ¡este Guille!)

Equals, un método que comprueba si dos objetos son "exactamente" iguales.

Nota:
Aquí vamos a ver sólo lo que habría que añadir a la clase cColega, el código anterior sigue siendo válido y es el mismo que el que vimos en la entrega 37, con el añadido del método Clone de la entrega 39.

Para hacer las cosas bien, vamos a empezar por crear un nuevo proyecto, al cual le añadiremos una Referencia que nos será útil, sino obligatoria, para cuando vayamos a crear la colección.
Abre el Visual Basic, crear un proyecto de tipo EXE, (automáticamente se añadirá un formulario llamado Form1), añade la clase cColega, (te recomendaría que hicieras una copia de la clase anterior), y del menú Proyecto, selecciona Referencias... del cuadro de diálogo que te muestra, busca el elemento "OLE Automation" y pulsa en el checkBox para seleccionarlo, esta referencia yo suelo añadirla en todos los proyectos. En la siguiente imagen puedes ver el cuadro de diálogo y la referencia que tenemos que seleccionar (recuerda que el idioma de mi Visual Basic está en inglés, así que, seguramente lo que tu veas sea diferente... por el idioma, ya que el resultado es el mismo).


Añadir OLE Automation en Referencias del proyecto


Te recuerdo que esto que acabamos de hacer no tiene nada que ver con el método Equals, sino para que nuestra clase-colección se pueda usar con bucles For Each.

¡Ahora sí!
Veamos el código del método Equals, (realmente una función que devolverá True o False, según los elementos comparados sea o no iguales).
Primero veremos el código y después te lo explico para que no te quede duda... aunque es muy simple, como podrás comprobar.

Public Function Equals(ByVal unColega As cColega) As Boolean
    ' Comprueba si el objeto pasado en el parámetro es igual a este objeto
    Dim iguales As Boolean
    '
    ' Si el tipo pasado no es del tipo cColega, no son iguales
    If (TypeOf unColega Is cColega) Then
        ' Comprobar si cada una de las propiedades son iguales,
        ' al estar anidadas,
        ' sólo serán iguales si llega a la última comparación.
        With unColega
            If .email = Me.email Then
                If .FechaNacimiento = Me.FechaNacimiento Then
                    If .Nombre = Me.Nombre Then
                        iguales = True
                    End If
                End If
            End If
        End With
    Else
        ' Esta asignación no es necesaria,
        ' ya que el valor por defecto de la variable iguales es False
        iguales = False
    End If
    Equals = iguales
End Function

Veamos qué es lo que hace esta función.
Como esta función comparará el objeto actual con otro del mismo tipo, (en este caso del tipo cColega), el parámetro pasado debe ser del tipo cColega. Fíjate que he usado ByVal, pero lo mismo da si se usa ByRef, ya que el Visual Basic no permitirá que el parámetro sea de otro tipo... si lo intentas, recibirás un error del tipo TypeMismatch si el parámetro es un objeto, pero no del tipo cColega, o bien un error indicándote que el parámetro debe ser un objeto.

Aún así... es decir, sabiendo que a esa función sólo llegarán objetos del tipo cColega, he añadido una comparación que comprueba que realmente el objeto es del tipo cColega, esto sólo es para que sepas cómo comprobarías que el objeto sea de ese tipo... ya que es útil si nos decidiéramos a cambiar el tipo del parámetro por otro más genérico, ya sea Object o Variant, (preferiblemente Variant, si lo quieres hacer "súper" genérico); de esta forma, el Visual Basic no daría ningún error y será el código de la función el que se encargue de comprobar si son o no iguales.

Esta sería la forma de declarar la función, ya que el código contenido en ella seguiría siendo el mismo que hemos visto hace unas líneas:

Public Function Equals(ByVal unColega As Variant) As Boolean

Es tu decisión utilizar una u otra forma, todo depende de cómo quieras que el Visual Basic actúe e incluso cómo de cuidadoso debe ser el programador que utilice tu clase... si es el programador el que debe tener en cuenta si el objeto pasado a la función es del tipo correcto o no, utiliza la primera versión, pero si quieres "curarte en salud" y que no se produzca un error porque ese programador no ha sido lo suficientemente cuidados, utiliza la segunda.
(Guille, creo que si le das tu opinión personal... pues...)
Vale, ¿cual te recomiendo que uses? Yo me inclinaría por la versión "más estricta", ya que se supone que ese método sirve para comprobar si dos objetos del tipo cColega son iguales, por tanto el que utilice nuestra clase será el que deba tener en cuenta que así sea...
Pero por otro lado... (se nota que el Guille es Géminis y eso de la doble personalidad...), creo que no hay que dar por hecho que el programador será cuidadoso, por tanto, en los ejemplos, usaremos la segunda versión, la que tiene el parámetro del tipo Variant.
De esta forma, podremos hacer algo como esto:
(se supone que tenemos una variable a nivel de módulo llamada mColega, que es del tipo cColega y que se ha instanciado correctamente)

    ' comparar un objeto del tipo cColega con otro de otro tipo diferente
    Dim otro As Variant
    '
    otro = "Pepe"
    '
    If mColega.Equals(otro) Then
        MsgBox "Los dos objetos son iguales"
    Else
        MsgBox "Los dos objetos son diferentes"
    End If

También podríamos hacer esto, y funcionaría correctamente:

    ' comparar un objeto del tipo cColega con otro de otro tipo diferente
    Dim otro As Variant
    '
    Set otro = mColega
    '
    If mColega.Equals(otro) Then
        MsgBox "Los dos objetos son iguales"
    Else
        MsgBox "Los dos objetos son diferentes"
    End If

En el primer caso nos diría que son diferentes y en el segundo que son iguales.

Nota:
En el segundo ejemplo, en el que se asigna a la variable otro el objeto del tipo cColega, también se podría usar la primera versión de la función Equals.

Bien, ya que sabemos cómo comprobar si dos objetos son iguales... (¡Eh! ¡Guille! que dijiste que ibas a explicar cómo funcionaba esto... y te has enrollao tanto con lo de los tipos... que...) ...esto... pues sí... veamos cómo funciona todo el código...
Ya ha quedado claro para que usamos el TypeOf: para comprobar si el objeto pasado en el parámetro es del tipo cColega, en caso de que así sea, se comprueba cada una de las propiedades del objeto unColega con el contenido en esta clase, (para ello usamos Me).
Como puedes comprobar, he anidado cada comparación dentro de otra, de forma que sólo se comprobará la siguiente si la anterior ha dado como resultado un valor verdadero. En caso de que alguna de esas comparaciones "falle", es decir, la misma propiedad de los dos objetos no sean iguales, el contenido de la variable iguales seguirá teniendo el valor que Visual Basic le asigna por defecto, en el caso de las variables de tipo Boolean, será False. Por tanto la función devolverá un valor falso... cosa que se hace en la última línea de la función al devolver el valor contenido en la variable iguales.
Sólo decirte, que la parte Else de la comparación que comprueba si unColega es del tipo cColega, no es necesaria, ya que le estamos asignando el valor que ya tiene por defecto.

Por tanto, el código completo de la función Equals es este:

Public Function Equals(ByVal unColega As Variant) As Boolean
    ' Comprueba si el objeto pasado en el parámetro es igual a este objeto
    Dim iguales As Boolean
    '
    ' Si el tipo pasado no es del tipo cColega, no son iguales
    If (TypeOf unColega Is cColega) Then
        ' Comprobar si cada una de las propiedades son iguales,
        ' al estar anidadas,
        ' sólo serán iguales si llega a la última comparación.
        With unColega
            If .email = Me.email Then
                If .FechaNacimiento = Me.FechaNacimiento Then
                    If .Nombre = Me.Nombre Then
                        iguales = True
                    End If
                End If
            End If
        End With
    End If
    Equals = iguales
End Function

Ahora sí... ¡por fin! podremos ver lo que realmente teníamos que ver...

Crear clases personalizadas

Vamos a empezar la clase-colección desde el principio, para que no te pierdas ningún detalle.
Para empezar, añade una nueva clase al proyecto, en la ventana de propiedades, cámbiale el nombre para que sea cColegas.
Muestra la ventana del código y añade la siguiente declaración:

Option Explicit

Private mCol As Collection

El Option Explicit se supone que ya estaría... si es que sigues mis consejos... y si no es así... yo de ti me lo pensaría "forastero" (musiquilla de spaghetti-western)
A continuación declaramos un objeto del tipo Collection que será el que se encargue de contener los objetos de nuestra colección.
Con esta línea, simplemente declaramos la variable, pero no instanciamos (o creamos) el objeto, eso lo haremos en el evento Class_Initialize, el cual se ejecutará cuando se cree un nuevo objeto de nuestra colección.
Por otro lado, cuando un objeto se destruye, se ejecuta el evento Class_Terminate, por tanto será en ese procedimiento donde destruiremos nuestra colección.
Veamos el código de esos dos procedimientos:

Private Sub Class_Initialize()
    Set mCol = New Collection
End Sub

Private Sub Class_Terminate()
    Set mCol = Nothing
End Sub

Es recomendable que siempre crees los nuevos objetos de forma separada de la declaración, ya que si declaras la variable de esta otra forma:

Private mCol As New Collection

Consigues lo mismo, pero también añades "sobrecarga" o trabajo extra al Visual Basic, ya que cada vez que se utilice la variable mCol, el VB tendrá que comprobar si ya existe una instancia en memoria y si no es así, tiene que crearla...
Para que lo comprendas mejor, si tenemos el siguiente código:

i = mCol.Count

el Visual Basic realmente haría lo siguiente:

If mCol Is Nothing Then
    Set mCol = New Collection
End If
i = mCol.Count

Es decir, comprueba si ya está creada la colección y si no es así, crea una nueva instancia, con lo cual, ¡cada vez que se vaya a usar el objeto, Visual Basic tiene que comprobar si ya está creado el objeto en memoria!
Creo que esto ya te lo he dicho antes, si no es así... apúntatelo y que no se te olvide...

Una vez aclarado estoy ya que tenemos el código que declara el objeto Collection que usará nuestra clase-colección, empecemos por los métodos que suelen incluirse en todas las colecciones.

Count, nos indicará cuantos elementos hay en la colección, la codificación de este método es bien simple, ya que simplemente llamamos al método con el mismo nombre que incorpora la colección.
Este método (realmente es una función), devolverá cero si no hay ningún elemento en la colección o la cantidad de elementos que tengamos almacenados.
Veamos el código del método Count:

Public Function Count() As Long
    Count = mCol.Count
End Function

Remove, eliminará un elemento de la colección.
El código también es bastante simple, ya que utilizaremos el método Remove del objeto mCol:

Public Sub Remove(ByVal Index As Variant)
    mCol.Remove Index
End Sub

El parámetro lo declaramos con el tipo Variant para que nos permita usar las dos formas que las colecciones de Visual Basic permite:
1- Utilizar un índice numérico que será la posición del objeto que queremos borrar, el primer elemento será el 1.
2- Utilizar la clave que hemos usado para el objeto que queremos eliminar.
En el caso de que el elemento que queremos eliminar no esté en la colección, se producirá un error, por tanto podríamos añadir detección de errores, para evitar que eso ocurra. Pero, si no se produce un error, el usuario de nuestra clase no sabrá que ese elemento no estaba... y por tanto no "prestaría" atención y podría creer que está trabajando de forma correcta, por tanto vamos a dejar el código así, tal como está.

Si quieres crear una versión del método Remove que tenga en cuenta lo que te acabo de comentar, tendrás que declarar Remove como una función y si se produce un error devolver un valor Falso y en caso de que se borre correctamente el elemento indicado, devolver un valor Verdadero, de forma que se pueda usar de la siguiente forma:

' oColegas es una variable del tipo cColegas
If oColegas.Remove(3) Then
    Text1 = "Se ha borrado correctamente"
Else
    Text1 = "No se ha borrado el elemento indicado"
End If

La forma de declarar esta versión de Remove no te lo muestro, para que lo hagas como "ejercicio".

Add, para añadir nuevos elementos a la colección.
El método para añadir nuevos elementos a la colección lo podemos escribir de varias formas:
1- Tal como se hace con las colecciones de Visual Basic:

mCol.Add Item, Key, before, after

2- Añadiendo un objeto del tipo que la colección contendrá y opcionalmente indicando la clave:

mCol.Add unColega
mCol.Add unColega, Key

Aunque esta forma de hacerlo sería igual que el del punto 1, ya que en esa forma de hacerlo, el único parámetro obligatorio es el primero y los demás opcionales.

3- Como se hace en algunas de las colecciones de VB, en las que se indica algunos de los valores de las propiedades y que devuelva un objeto del tipo cColega.

Dim oColega As cColega
Set oColega = mColegas.Add("Guille", "mensaje@elguille.info")

El cual también permitiría añadir elementos a la colección sin necesidad de "recibir" el objeto:

mColegas.Add "otro Guille", "guille@mvps.org"

4- Una mezcla de los otros:
 -Si el primer parámetro es un objeto del tipo cColega funcionará como lo indicado en el punto 2
 -Si el primer parámetro NO es un objeto del tipo cColega, supondremos que es el nombre.
De esta forma podríamos añadir objetos a la colección de cualquiera de estas formas:

Set oColega = New cColega
oColega.Nombre = "el Guille"
oColega.email = "mensaje@elguille.info"
oColega.FechaNacimiento = "07/06/1957"
'
mColegas.Add oColega
mColegas.Add oColega, "mensaje@elguille.info"
mColegas.Add oColega, "mensaje@elguille.info", "07/06/1957"
mColegas.Add "el Guille", "mensaje@elguille.info"
mColegas.Add "el Guille", "mensaje@elguille.info", "07/06/1957"
Set oColega = mColegas.Add("el Guille")
Set oColega = mColegas.Add("el Guille", "mensaje@elguille.info")
Set oColega = mColegas.Add("el Guille", "mensaje@elguille.info", "07/06/1957")

Si después de estos ejemplos no te sabes mi dirección de correo... en fin.

Es decir, podemos crear nuevos objetos de formas muy variadas...
Pero lo interesante es saber ¿cómo se hace esto?
Y la verdad es que no se si sería conveniente mostrarte el código... (venga Guille, no te hagas de rogar)
No es por hacerme de rogar ni aparentar "suspense", es que puede ser que te líes... así que, lo mejor es mostrarlos todos, para que vayas comprendiendo mejor las opciones. (y así de camino la entrega es más larga... ¡no sabes ná!)

1- Al estilo del objeto Collection de Visual Basic:

Public Sub Add(ByVal Item As cColega, _
               Optional ByVal key As String, _
               Optional ByVal before As Variant, _
               Optional ByVal after As Variant)
    mCol.Add Item, key, before, after
End Sub

Aquí lo que hacemos es recibir cuatro parámetros, los tres últimos opcionales. El primero debe ser del tipo cColega, ya que esta colección sólo admitirá objetos de ese tipo.

2- Este es como el anterior, pero sin los dos últimos parámetros, aunque el segundo será opcional.

3- En esta forma de hacerlo, los parámetros serán equivalentes a las propiedades del objeto, por tanto se usarán para crear un nuevo objeto del tipo cColega y ese nuevo objeto se añadirá a la colección y se devolverá por la función.

Public Function Add(ByVal elNombre As String, _
                    ByVal elEmail As String, _
                    Optional ByVal laFecha As String) As cColega
    Dim tColega As cColega
    Set tColega = New cColega
    '
    With tColega
        .email = elEmail
        .FechaNacimiento = laFecha
        .Nombre = elNombre
    End With
    ' usamos el email como clave, para asegurarnos de que sea único
    mCol.Add tColega, elEmail
    ' Devolver el objeto recién creado
    Set Add = tColega
End Function

4- Por último, la versión "multi-uso", que se podrá usar como cualquiera de las dos anteriores:

Public Function Add(ByVal uno As Variant, _
                    Optional ByVal elEmail As String, _
                    Optional ByVal laFecha As String) As cColega
    Dim tColega As cColega
    '
    ' si el tipo del primer parámetro es un objeto del tipo cColega
    If TypeOf uno Is cColega Then
        Set tColega = uno
        ' si se indican los otros parámetros...
        If Len(elEmail) > 0 Then
            tColega.email = elEmail
        End If
        If Len(laFecha) > 0 Then
            tColega.FechaNacimiento = laFecha
        End If
    Else
        ' sino, suponemos que el primer parámetro es el nombre
        Set tColega = New cColega
        '
        With tColega
            .email = elEmail
            .FechaNacimiento = laFecha
            .Nombre = uno
        End With
    End If
    ' usamos el email como clave, para asegurarnos de que sea único
    mCol.Add tColega, tColega.email
    '
    ' Devolver el objeto recién creado
    Set Add = tColega
End Function

Para conseguir nuestro objetivo, el primer parámetro será de tipo Variant, con idea de que permita "cualquier" tipo de dato en ese parámetro.
A continuación comprobamos si dicho parámetro es un objeto del tipo cColega, de ser así, se asignará a la variable que hemos creado dentro del procedimiento y opcionalmente se asignarán el resto de los parámetros, siempre que éstos se hayan indicado con algo diferente a una cadena vacía.
Si el primer parámetro NO es del tipo cColega, vamos a asumir que es el valor que asignaremos a la propiedad Nombre del objeto que queremos crear. En caso de usarla de esta segunda forma, deberíamos especificar también el email, ya que es esa propiedad la que usamos como clave del objeto, por la sencilla razón de que no tendremos dos colegas con una misma cuenta de correo... Debido a que es la propia clase cColega la que comprueba si lo que se asigna a la propiedad email es una cuenta de correo, si ese segundo parámetro no lo fuera, se produciría un error.

Item, nos permitirá acceder a un elemento de la colección, bien indicando el valor de la clave o una posición dentro de la colección.
Por tanto podríamos acceder a un elemento de la colección de cualquiera de estas dos formas:

mColegas.Item("mensaje@elguille.info").Nombre = "Guille"
mColegas.Item(2).Nombre = "Guille"

El código más simple para esta función sería el siguiente:

Public Function Item(ByVal Index As Variant) As cColega
    Set Item = mCol(Index)
End Function

El problema se presentará si el elemento al que queremos acceder no existe en la colección.
Pero eso podemos solventarlo con algunos de los métodos que después añadiremos.

Lo que sería interesante es poder usar este método sin tener que indicar la palabra Item, tal como se hace en las colecciones propias de Visual Basic, de forma que esas dos asignaciones pudiéramos hacerlas de esta otra forma:

mColegas("mensaje@elguille.info").Nombre = "Guille"
mColegas(2).Nombre = "Guille"

Para conseguirlo, tenemos que indicarle al Visual Basic que el método Item es el método por defecto de la clase.
Para ello, tenemos que posicionarnos en la declaración del procedimiento (aunque no es estrictamente necesario), ahora pulsa en el menú Tools>Procedure Attributes... (Herramientas/Atributos de procedimientos), tal como se muestra en esta imagen:

Del cuadro de diálogo que se muestra, selecciona Item de la lista superior, pulsa en el botón Avanzado para que se muestre la otra parte de la ventana y en la lista ID de procedimiento selecciona (Default), tal como se muestra en esta imagen:


Para que una propiedad sea la predeterminada

Y después de pulsar Intro para aceptar las selecciones, el método Item será el predeterminado y por tanto no será necesario indicarlo para usarlo.

Crear un "enumerador" para nuestra colección.
Para terminar con la colección, (no te preocupes que no me he olvidado de que hay más cosas, me refiero a terminar con los métodos que se incluyen en las colecciones normales), vamos a crear un método "especial" que permita recorrer los elementos de la colección usando For Each.
Para conseguir nuestro objetivo, vamos a crear un método que se llame NewEnum y que sea del tipo IUnknown, no voy a entrar en detalles del "por qué", simplemente veremos el código y la forma de "configurar" este procedimiento en el cuadro de diálogo del atributos de procedimientos.
Empecemos por el código:

Public Function NewEnum() As IUnknown
    ' Debe ser un miembro oculto y
    ' el id del procedimiento debe ser -4
    '
    Set NewEnum = mCol.[_NewEnum]
End Function

Sólo aclararte que los corchetes son necesarios ya que el nombre de la propiedad contiene un carácter no válido, (empieza por un guión bajo), éste indica que es un miembro oculto...
Pero sólo con esto no conseguimos nuestro objetivo, tal como se indica en los comentarios, el procedimiento NewEnum debe estar oculto y tener un ID de procedimiento con valor -4, esto lo hacemos mediante el cuadro de diálogo usado para asignar la propiedad predeterminada, tal como podemos ver en la siguiente imagen:


Para crear un enumerador que permita recorrer la colección usando For Each

Una vez hecho esto, podemos recorrer los elementos de la colección de esta forma:

' mostrar el contenido de la colección
Dim oColega As cColega
'
Text1 = ""
For Each oColega In mColegas
    Text1 = Text1 & oColega.Nombre & ", " & _
                    oColega.email & ", " & _
                    oColega.FechaNacimiento & vbCrLf
Next

Aunque también podemos hacerlo de la forma clásica:

Dim i As Long
'
Text1 = ""
For i = 1 To mColegas.Count
    Text1 = Text1 & mColegas(i).Nombre & ", " & _
                    mColegas(i).email & ", " & _
                    mColegas(i).FechaNacimiento & vbCrLf
Next

Ampliando la funcionalidad de la colección.
Bien, con lo visto hasta ahora, tenemos la misma funcionalidad que la mayoría de las colecciones de Visual Basic.
Vamos a añadir nuevos métodos.

Clear, eliminar el contenido de la colección.
Empezaremos con uno que nos permita eliminar el contenido de la colección y así dejarla preparada para añadir nuevos elementos en una colección totalmente vacía.
Si no tuviéramos este método, tendríamos que hacerlo de esta forma:

Set mColegas = Nothing
Set mColegas = New cColegas

Y eso es precisamente lo que haremos en el método Clear... es que es la forma más rápida y "limpia" de eliminar los elementos, pero por supuesto lo que borramos es el contenido de la colección privada de nuestra clase-colección.

Public Sub Clear()
    Set mCol = Nothing
    Set mCol = New Collection
End Sub

Exists, comprobar si un elemento está incluido en la colección.
Para comprobar si un elemento está en la colección podemos hacerlo de dos formas, una sería recorriendo cada uno de los elementos de la colección y comprobar si el indicado está contenido... pero hay otra forma más fácil, y puede que más rápida, que es aprovecharse de que se produce un error cuando se quiere acceder a un elemento y dicho elemento no existe... veamos el código de esta última forma:

Public Function Exists(ByVal Index As Variant) As Boolean
    Dim tColega As cColega
    '
    On Error Resume Next
    '
    Set tColega = mCol(Index)
    ' si se produce un error es que no existe ese elemento
    If Err.Number <> 0 Then
        Exists = False
    Else
        Exists = True
    End If
End Function

Nota:
A este método también podríamos llamarlo Contains, (que es como suele llamarse en las colecciones de la nueva versión .NET de Visual Basic).

Clone, crear una copia "independiente" de la colección.
Tal como hicimos con la clase cColega, vamos a crear un método que permita hacer una copia independiente de la colección.
Te recuerdo que cuando asignamos un objeto a otro, lo único que conseguimos es crear una nueva referencia que apunta al mismo objeto que ya está creado en la memoria, es decir sólo existe un objeto en la memoria.
Pero mediante este método creamos otra copia en la memoria, de forma que los cambios realizados en ella no afectarán al original.
El código es bien simple, ya que vamos a aprovecharnos de que la clase cColega ya tiene un método que permite crear copias de ese objeto.
Veamos el código:

Public Function Clone() As cColegas
    ' Hacer una copia de esta colección
    '
    Dim tColega As cColega
    Dim tColegas As cColegas
    '
    Set tColegas = New cColegas
    '
    ' Añadir a la nueva colección una copia de cada elemento
    For Each tColega In mCol
        tColegas.Add tColega.Clone()
    Next
    '
    Set Clone = tColegas
End Function

Lo que hacemos es crear una nueva colección del mismo tipo de la clase, recorremos cada uno de los elementos contenidos y añadimos una copia, con idea de que se cumpla nuestro objetivo, tener copias independientes.

Y para terminar (¡ya era hora!), nos queda por ver cómo sería el código para el método para saber si dos colecciones son iguales:

Equals, saber si dos colecciones son iguales.
El código de este método es algo diferente al de la clase normal, entre otras cosas porque no sólo tenemos que comprobar si las propiedades de cada uno de los elementos son exactamente iguales, (aunque de ese detalle se encarga el método Equals de cada uno de los objetos contenidos en la colección), sino que también tenemos que tener en cuenta otras cosas... aunque al fin y al cabo todo es para poder saber si las dos colecciones son iguales...
Veamos el código:

Public Function Equals(ByVal compararCon As Variant) As Boolean
    Dim iguales As Boolean
    Dim oColegas As cColegas
    Dim oColega As cColega
    '
    ' Sólo serán iguales si es un objeto del tipo cColegas
    If TypeOf compararCon Is cColegas Then
        Set oColegas = compararCon
        ' sólo serán iguales si tienen los mismos elementos
        If oColegas.Count = mCol.Count Then
            ' asumimos que serán iguales
            iguales = True
            For Each oColega In mCol
                ' si no existe o no son iguales...
                If oColegas.Exists(oColega.email) = False Then
                    iguales = False
                    Exit For
                ElseIf oColega.Equals(oColegas(oColega.email)) = False Then
                    iguales = False
                    Exit For
                End If
            Next
        End If
    End If
    Equals = iguales
End Function

Lo primero que hacemos es comprobar que el objeto es del tipo adecuado, si es así, asignamos ese objeto a una variable del tipo cColegas, (esto simplemente es para poder usar las propiedades y métodos, ya que si usamos la variable pasada como parámetro, al ser del tipo Variant, no nos mostraría los miembros).
A continuación comprobamos que el número de elementos de las dos colecciones sean iguales, en caso de que no lo sean, el valor de la variable iguales no cambiará, con lo cual la función devolverá un valor False.
A partir de este punto asignamos un valor verdadero a esa variable, esto es así porque las siguientes comparaciones buscan valores que diferencien a ambas colecciones... Para ello, hacemos un bucle que recorra todos los elementos, comprobamos si cada uno de los elementos existe en la otra colección, si no es así, asignamos un valor falso y salimos del bucle. Si el email es igual en los dos objetos, comprobamos si realmente son iguales, en caso de que no lo sean, asignamos un valor falso y salimos del bucle.
Puede que pienses que esto se podría haber simplificado usando sólo esta comparación:

If oColega.Equals(oColegas(oColega.email)) = False Then
    iguales = False
    Exit For
End If

Pero esto daría error si en la colección pasada por parámetro no contiene el objeto que se comprueba.
¿Por qué?
Como hemos visto, si hacemos esto: oColegas(laClave), que es lo mismo que si hacemos esto otro: oColegas.Item(laClave), y dicha clave no está contenida en la colección, se producirá un error, ya que en el método Item de la colección no se hace ningún tipo de detección de errores, por tanto si el elemento al que se quiere acceder no existe, se produce un error. (Guille eso lo dijiste antes..., ya lo sé, pero así seguro que queda más claro...)
Por esa razón se hace primero la comprobación de que exista el objeto en la colección.

Y hasta aquí hemos llegado en esta entrega que tanto se ha hecho esperar... aunque confío que con lo que me he enrollado, no te quejes y espero que tampoco te "empaches" demasiado... je, je.

Posiblemente, en la próxima entrega veremos más cosas referentes a las clases... pero no nos precipitemos que si después cambio de idea... lo mismo me regañas...

Nos vemos
Guillermo
P.S.
Aquí tienes el código completo de las clases y un proyecto de prueba: basico42_cod.zip 6.01 KB

P.S.2:
Aquí te explico "someramente" lo que es eso del For Each, ya que según parece sólo lo dije "de pasada" en la entrega 38.

For Each, otra forma de hacer bucles.

Este tipo de bucles, a diferencia del clásico bucle For al que hay que indicarle una variable numérica, se utiliza con una variable de un tipo de objeto que debe estar contenido en una colección, de forma que se pueda recorrer cada uno de los elementos de dicha colección que sean del tipo usado junto a For Each.
La forma de usarla sería:
For Each elemento In laColección
    ' lo que haya que hacer con el elemento
Next
...


 
entrega anterior ir al índice siguiente entrega

Ir al índice principal del Guille