martes, 26 de julio de 2016

Comparación de Igualdad en C# | Parte 2/5 | Protocolos de Igualdad Estándar: ==, != y virtual Object.Equals y static object.Equals

Índice

1. Introducción
2. Palabras Clave
3. Protocolos de Igualdad Estándar
3.1 Generalidades
3.2 Operadors == y !=
3.3 Método virtual Object.Equals
3.4 Método static object.Equals
4. Conclusiones
5. Literatura & Enlaces

1. Introducción

Esta segunda parte de Comparación de Igualdad en C# discute y presenta detalles prácticos de los protocolos de igualdad en C#. Estos protocolos permiten al programador definir un concepto de igualdad para tipos de datos por valor y por referencia. Aquí se explica claramente las particularidades técnicas de los operadores estáticos == y !=, además se explica por qué existe un grado de complejidad con los operadores estáticos y la funcionalidad del método Equals. Para lo anterior ha sido necesario exponer las diferencias sintácticas y semánticas de los métodos virtual Object.Equals y static object.Equals.

2. Palabras Clave

  • Comparación de igualdad
  • Igualdad
  • Igualdad por valor
  • Igualdad por referencia
  • Object.Equals
  • object.Equals
  • Tipo por referencia
  • Tipo por valor

3. Protocolos de Igualdad Estándar

3.1 Generalidades

En .NET Framework, en particular en C#, existen tres protocolos estándar que un tipo de dato puede implementar para efectuar la comparación de igualdad (Albahari, 2012) con base a un contexto definido
  1. Los operadores == y !=
  2. El método virtual Object.Equals
  3. La interfaz genérica IEquatable<T>

3.2 Operadores == y !=

En el primer artículo de esta serie Comparación de Igualdad en C# | Parte 1/5 | Tipos de Igualdad se expusieron ejemplos de uso de los operadores == y != para efectuar comparaciones de igualdad y diferencia.


Tanto == y != son operadores que el compilador de C# resuelve de modo estático, y es preciso mencionar que internamente están implementados como funciones static. En consecuencia, en tiempo de compilación el compilador toma la decisión sobre qué tipo particular de comparación realizar, así que ningún comportamiento virtual -es decir dependiente de un tipo de dato en particular- es llevado a cabo.


Se pone por caso el siguiente ejemplo de código fuente: 


int x = 5;
int y = 5;

Console.WriteLine(x == y); // True


El compilador en esta situación ata el operador == al tipo de dato por valor int. Esto ocurre en tiempo de compilación.


Mientras que con estas líneas de código 

object x = 5;
object y = 5;

Console.WriteLine(x == y); // False


Aquí, el compilador ata el operador == al tipo de dato object lo que conduce a una comparación de igualdad del tipo por referencia: se quiere determinar si las variables x e y corresponden con la misma dirección en memoria; para esta situación no es así a pesar que contengan el mismo valor 5.

3.3 Método virtual Object.Equals

¿Cómo puede el programador realizar una comparación de igualdad por valor para el segundo ejemplo de la sección 3.2? La clase Object ("Object Class", 2016) posee el miembro virtual Equals(object) para comprobar si una instancia es igual otra en cuanto a sus valores: 

object x = 5;
object y = 5;

Console.WriteLine(x.Equals(y)); // True

A diferencia del operador estático ==, el cual es resuelto en tiempo de compilación, Equals es resuelto en tiempo de ejecución. La resolución de tipos en tiempo de ejecución para esta comparación de igualdad invoca el método Int32.Equals, el cual corresponde con el tipo de dato de los valores 5.

Por otro lado, el método Equals realiza comparación estructural para estructuras; es decir que la comparación de igualdad se lleva a cabo campo por campo para un par de estructuras dadas (Albahari, 2012).

La reunión y análisis de estos casos de comparación de igualdad, quizás lleve al programador a preguntarse por qué existe un grado de entretejimiento o complejidad inherente a estas operaciones. De acuerdo con Albahari (2012), las razones por las cuales en el diseño de C# no se considera al operador == como virtual, como ocurre con Object.Equals(object), son: 
  1. El método Equals falla, es decir que lanza una excepción NullReferenceException cuando el primer operador es null.
  2. Por motivos de rendimiento el operador == resuelve los tipos de datos de los operandos de forma muy rápida.
  3. Para determinados contextos es preferible tener dos definiciones de igualdad a través de == y Equals.



El programador puede recurrir a una implementación de código como la siguiente para comparar dos objetos de cualquier tipo: 

public static bool SonIguales(object obj1, object obj2)
{
if (obj1 == null)
{
return obj2 == null;
}

return obj1.Equals(obj2);
}



Con esto se evita que el primer argumento sea null. Esta es la solución a la primera propuesta que corresponde con 

public static bool SonIguales(object obj1, object obj2)
{
return obj1.Equals(obj2);
}


Con la primera solución del método SonIguales se evita la excepción NullReferenceException cuando el primer argumento es null, con la anterior no.

3.4 Método static object.Equals

Existe en la clase Object el método static Equals(Object, Object) ("Object.Equals Method (Object, Object3)", 2016), el cual sirve para determinar si dos instancias de dos tipos de objetos son iguales.


object.Equals corresponde con la implementación oficial al método SonIguales de la sección inmediatamente anterior. A esto hay que agregar, que object.Equals utiliza un algoritmo de comparación de igualdad robusto y seguro frente a argumentos null (Albahari, 2012).


El siguiente ejemplo de código fuente C# de muestra lo anterior (escrito en LINQPad): 

object x = 7;
object y = 7;

Console.WriteLine(object.Equals(x, y)); // True

x = null;
Console.WriteLine(object.Equals(x, y)); // False

y = null;
Console.WriteLine(object.Equals(x, y)); // False


Otra aplicación sobresaliente del método static Equals tiene que ver con la comparación de instancias de tipos de datos genéricos.

Archivo C# TipoDatoGenericoPersonalizado.cs [Enlace alternativo][Enlace alternativo]:

Si el programador hubiera especificado en el predicado de la sentencia de control if cualquiera de los operadores estáticos == o != -if(nuevoValor == _valor)- la compilación no se hubiera completado satisfactoriamente. ¿La razón? Los operadores estáticos no están permitidos para operandos genéricos.


[Nota: Más adelante se estudia la clase genérica EqualityComparer<T> para efectuar comparaciones más sofisticadas para tipos de datos genéricos: if(!EqualityComparer<T>.Default.Equals(nuevoValor, _valor)).]

4. Conclusiones

Se comprendió que los operadores == y != operan a nivel estático y durante tiempo de compilación. Se sabe ahora que el método virtual Equals opera en tiempo de ejecución y efectúa una comparación de igualdad por referencia. Se estudió el método static object.Equals para comparar instancias de objetos de cualquier tipo de dato.

La siguiente parte estudia el método object.ReferenceEquals y la interfaz IEquatable<T>.

5. Literatura & Enlaces

Albahari, J., Albahari, B. (2012). C# 5.0 in a Nutshell. United States: O'Reilly Media.

Comparación de Igualdad en C# | Parte 1/5 | Tipos de Igualdad (2016, julio 26). Recuperado desde: https://ortizol.blogspot.com/2016/07/comparacion-de-igualdad-en-csharp-parte-1-5-tipos-de-igualdad.html
Object Class (System) (2016, julio 26). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.object.aspx
Object.Equals Method (Object) (System) (2016, julio 26). Recuperado desde: https://msdn.microsoft.com/en-us/library/bsc2ak47.aspx
Int32.Equals Method (Int32) (System) (2016, julio 26) Recuperado desde: https://msdn.microsoft.com/en-us/library/tf6tw08e.aspx



O

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.