Ahora que sabemos lo que es la
herencia es el momento apropiado para explicar que en .NET todos los tipos que
se definan heredan implícitamente de la clase System.Object
predefinida en la BCL, por lo que dispondrán de todos los miembros de ésta. Por
esta razón se dice que System.Object es la raíz de la jerarquía de objetos de .NET.
A continuación vamos a explicar
cuáles son estos métodos comunes a todos los objetos:
- public virtual bool Equals(object
o): Se usa para comparar el objeto sobre el que se aplica con
cualquier otro que se le pase como parámetro. Devuelve true si ambos objetos son iguales y false en caso contrario.
La
implementación que por defecto se ha dado a este método consiste en usar
igualdad por referencia para los tipos por referencia e igualdad por valor para
los tipos por valor. Es decir,
si los objetos a comparar son de tipos por referencia sólo se devuelve true
si ambos objetos apuntan a la misma referencia en memoria dinámica, y si los
tipos a comparar son tipos por valor sólo se devuelve true si todos
los bits de ambos objetos son iguales, aunque se almacenen en posiciones diferentes de memoria.
Como se ve,
el método ha sido definido como virtual, lo que permite que
los programadores puedan redefinirlo para indicar cuándo ha de considerarse que
son iguales dos objetos de tipos definidos por ellos. De hecho, muchos de los
tipos incluidos en la BCL cuentan con redefiniciones de este tipo, como es el
caso de string,
quien aún siendo un tipo por referencia, sus objetos se consideran iguales si apuntan a cadenas que
sean iguales carácter a carácter (aunque referencien a distintas direcciones de
memoria dinámica)
El siguiente
ejemplo muestra cómo hacer una redefinición de Equals() de
manera que aunque los objetos Persona sean
de tipos por referencia, se considere que dos Personas son
iguales si tienen el mismo NIF:
public override bool Equals(object
o)
{
if (o==null)
return
this==null;
else
return
(o is Persona) && (this.NIF == ((Persona) o).NIF);
}
Hay que tener
en cuenta que es conveniente que toda redefinición del método Equals()
que hagamos cumpla con una serie de
propiedades que muchos de los métodos
incluidos en las distintas clases de la BCL esperan que se cumplan. Estas
propiedades son:
q Reflexividad: Todo objeto
ha de ser igual a sí mismo. Es decir, x.Equals(x)
siempre ha de devolver true.
q Simetría: Ha de dar igual
el orden en que se haga la comparación. Es decir, x.Equals(y)
ha de devolver lo mismo que y.Equals(x)
.
q Transitividad: Si dos
objetos son iguales y uno de ellos es igual a otro, entonces el primero también
ha de ser igual a ese otro objeto. Es decir, si x.Equals(y)
e y.Equals(z)
entonces x.Equals(z) .
q Consistencia: Siempre que el
método se aplique sobre los mismos objetos ha de devolver el mismo resultado.
q Tratamiento de objetos nulos: Si
uno de los objetos comparados es nulo (null), sólo
se ha de devolver true si el otro también lo es.
Hay que
recalcar que el hecho de que redefinir Equals()
no implica que el operador de igualdad (==) quede también redefinido. Ello habría que
hacerlo de independientemente como se indica en el Tema 11: Redefinición de operadores.
- public virtual int GetHashCode():
Devuelve un código de dispersión (hash) que representa de forma numérica
al objeto sobre el que el método es aplicado. GetHashCode()
suele usarse para trabajar con tablas de dispersión, y se cumple que si
dos objetos son iguales sus códigos de dispersión serán iguales, mientras
que si son distintos la probabilidad de que sean iguales es ínfima.
En tanto que
la búsqueda de objetos en tablas de dispersión no se realiza únicamente usando
la igualdad de objetos (método Equals())
sino usando también la igualdad de códigos de dispersión, suele ser conveniente
redefinir GetHashCode()
siempre que se redefina Equals() De
hecho, si no se hace el compilador informa de la situación con un mensaje de
aviso.
- public virtual string ToString():
Devuelve una representación en forma de cadena del objeto sobre el que se
el método es aplicado, lo que es muy útil para depurar aplicaciones ya que
permite mostrar con facilidad el estado de los objetos.
La
implementación por defecto de este método simplemente devuelve una cadena de
texto con el nombre de la clase a la
que pertenece el objeto sobre el que es aplicado. Sin embargo, como lo habitual
suele ser implementar ToString() en
cada nueva clase que es defina, a continuación mostraremos un ejemplo de cómo
redefinirlo en la clase Persona
para que muestre los valores de todos los campos de los objetos Persona:
public override string ToString()
{
string
cadena = “”;
cadena
+= “DNI = “ + this.DNI + ”\n”;
cadena
+=”Nombre = ” + this.Nombre + ”\n”;
cadena
+=”Edad = ” + this.Edad + ”\n”;
return
cadena;
}
Es de reseñar
el hecho de que en realidad los que hace el operador de concatenación de
cadenas (+)
para concatenar una cadena con un objeto cualquiera es convertirlo primero en
cadena llamando a su método ToString() y luego realizar la concatenación de
ambas cadenas.
Del mismo
modo, cuando a Console.WriteLine()
y Console.Write()
se les pasa como parámetro un objeto lo que hacen es mostrar por la salida
estándar el resultado de convertirlo en cadena llamando a su método ToString();
y si se les pasa como parámetros una cadena seguida de varios objetos lo
muestran por la salida estándar esa cadena pero sustituyendo en ella toda
subcadena de la forma {<número>}
por el resultado de convertir en cadena el parámetro que ocupe la posición <número>+2 en la lista de valores de llamada al
método.
- protected object MemberWiseClone():
Devuelve una copia shallow copy
del objeto sobre el que se aplica. Esta copia es una copia bit a bit del
mismo, por lo que el objeto resultante de la copia mantendrá las mismas
referencias a otros que tuviese el objeto copiado y toda modificación que
se haga a estos objetos a través de la copia afectará al objeto copiado y
viceversa.
Si lo que
interesa es disponer de una copia más normal, en la que por cada objeto
referenciado se crease una copia del mismo a la que referenciase el objeto
clonado, entonces el programador ha de escribir su propio método clonador pero
puede servirse de MemberwiseClone()
como base con la que copiar los campos que no sean de tipos referencia.
- public System.Type GetType():
Devuelve un objeto de clase System.Type que
representa al tipo de dato del objeto sobre el que el método es aplicado.
A través de los métodos ofrecidos por este objeto se puede acceder a
metadatos sobre el mismo como su
nombre, su clase padre, sus miembros, etc. La explicación de cómo usar los
miembros de este objeto para obtener dicha información queda fuera del
alcance de este documento ya que es muy larga y puede ser fácilmente
consultada en la documentación que acompaña al .NET SDK.
- protected virtual void Finalize(): Contiene el código que se
ejecutará siempre que vaya ha ser destruido algún objeto del tipo del que
sea miembro. La implementación dada por defecto a Finalize()
consiste en no hacer nada.
Aunque es un
método virtual, en C# no se permite que el programador lo redefina
explícitamente dado que hacerlo es peligroso por razones que se explicarán en
el Tema 8: Métodos (otros lenguajes
de .NET podrían permitirlo).
Aparte de los métodos ya
comentados que todos los objetos heredan, la clase System.Object también
incluye en su definición los siguientes métodos de tipo:
·
public static bool Equals(object objeto1, object objeto2)
à
Versión estática del método Equals()
ya visto. Indica si los objetos que se le pasan como parámetros son iguales, y
para compararlos lo que hace es devolver el resultado de calcular objeto1.Equals(objeto2)
comprobando antes si alguno de los objetos vale null (sólo se
devolvería true
sólo si el otro también lo es)
Obviamente si
se da una redefinición al Equals() no estático, esta también se aplicará al
estático.
·
public static bool ReferenceEquals(object objeto1, object objeto2)
à
Indica si los dos objetos que se le pasan como parámetro se almacenan en la
misma posición de memoria dinámica. A través de este método, aunque se hayan
redefinido Equals() y el operador de
igualdad (==)
para un cierto tipo por referencia, se podrán seguir realizando comparaciones
por referencia.entre objetos de ese tipo en tanto que redefinir de Equals() no afecta a este método. Por ejemplo, dada la anterior redefinición de Equals() para objetos Persona:
Persona p = new
Persona(“José”, 22, “83721654-W”);
Persona q = new
Persona(“Antonio”, 23, “83721654-W”);
Console.WriteLine(p.Equals(q));
Console.WriteLine(Object.Equals(p, q));
Console.WriteLine(Object.ReferenceEquals(p,
q));
Console.WriteLine(p
== q);
La salida que por pantalla mostrará el código anterior es:
True
True
False
False
En los
primeros casos se devuelve true porque según la redefinición de Equals() dos personas son iguales si tienen el mismo
DNI, como pasa con los objetos p y q. Sin embargo, en los últimos casos se devuelve false porque
aunque ambos objetos tienen el mismo DNI cada uno se almacena en la memoria
dinámica en una posición distinta, que es lo que comparan ReferenceEquals() y
el operador == (éste último sólo por defecto)