jueves, 28 de julio de 2016

Comparación de Igualdad en C# | Parte 4/5 | Igualdad y Tipos de Datos Personalizados: Aspectos Generales

Índice

1. Introducción
2. Palabras Clave
3. Igualdad y Tipos de Datos Personalizados
3.1 Generalidades
3.2 Cambio del significado de igualdad
3.3 Comparación de igualdad  rápida en estructuras
4. Sobrescritura de la Semántica de Igualdad
4.1 Sobrescritura del método GetHashCode
4.2 Sobrescritura del método Equals
Conclusiones
Literatura & Enlaces

1. Introducción

Este artículo, así como el siguiente, describen cómo crear una implementación personalizada de comparación de igualdad. En este cuarto artículo de la serie Comparación de Igualdad en C# se discute principalmente los siguientes temas:
  • Cambio del significado de igualdad para un tipo de dato.
  • Hacer que el proceso de comparación de igualdad sea más rápido.
Aplicar estos procesos va a requerir los cambios en la semántica de igualdad por medio de la sobrescritura de GetHashCode y Equals; y, opcionalmente, la sobrecarga de los operadores != y ==, y la implementación de la interfaz IEquatable<T>.

2. Palabras Clave

  • Comparación de igualdad
  • Equals
  • GetHashCode
  • IEquatable<T>
  • Reflection
  • Sobrecarga
  • Sobrescritura

3. Igualdad y Tipos de Datos Personalizados

3.1 Generalidades

Hasta ahora se ha estudiado que C# considera dos tipos de igualdad respecto a los tipos de datos:
  • Los tipos de datos basados en valor usan igualdad por valor.
  • Los tipos de datos por referencia usan la igualdad por referencia (o igualdad referencial).
En cuanto a las estructuras, éstas basan la comparación de igualdad en la igualdad de valor estructural. La comparación se realiza por cada miembro de la estructura (Albahari J., 2012). No es necesario que el programador sobrescriba el método Equals; para ello se utiliza reflection (cfrReceta No. 3-12 en C#: Crear un Objeto con Reflection) sobre los campos de la estructura ("C# Equality and Comparisons", 2016).

Las siguientes subsecciones describen los escenarios donde tiene sentido alterar el comportamiento de la comparación de igualdad; y hacer que el proceso de comparación de igualdad se efectúe más rápido.

3.2 Cambio del significado de igualdad

En determinados escenarios el programador puede requerir cambiar el comportamiento de igualdad para el operador == y el método Equals. Como ejemplos de los tipos de datos que realizan la sobrescritura o cambio de comportamiento en Framework Class Library (FCL) están DateTimeOffset y double.

Para el caso de la estructura DateTimeOffset, la comparación se efectúa únicamente por el campo DateTime basado en UTC, ignorando el campo del offset. En cuanto a double (o double), la comparación de igualdad considera que dos valores NaN son diferentes (Albahari J., 2012).

En Albahari J. (2012) se menciona que para clases, el cambio de significado de igualdad por valor es preferible a igualdad referencial en ciertos escenarios. Por ejemplo, para las clases System.Uri System.String tiene más sentido efectuar la igualdad por valor: 

Ejemplo de uso

Uri uri1 = new Uri("http://www.uniandes.edu.co/");
Uri uri2 = new Uri("http://www.uniandes.edu.co/");
Console.WriteLine(uri1 == uri2); // True

String str1 = "Uniandes";
String str2 = "Uniandes";
Console.WriteLine(str1 == str2); // True

3.3 Comparación de igualdad rápida en estructuras

El algoritmo por defecto para la comparación de igualdad para estructuras consiste en comparar cada uno de los campos que pertenecen a este tipo de dato. A este tipo de comparación se le conoce como igualdad estructural. Internamente, este proceso es llevado a cabo con la técnica de reflection, y que en consecuencia tiene un efecto negativo en el rendimiento.

En la Figura 1 ("C# Equality and Comparisons) se muestra cómo el método Equals funciona para estructuras.
Figura 1. Equals en estructuras ("C# Equality and Comparisons).
Todas las estructuras, incluidas las integradas en .NET y las definidas por el programador, heredan de System.ValueType. Por lo tanto, el comportamiento de la comparación por igualdad por defecto de Equals consiste en efectuar la comparación campo por campo. En el diagrama de herencia de la Figura 1 la estructura Food y los demás tipos de datos que heredan de ValueType tendrán este comportamiento.

Como ya se mencionó, este tipo de comparación de igualdad por defecto no es eficiente. Una alternativa viable consiste en crear una implementación personalizada del método Equals o través de la sobrescritura del operador == y la implementación de la interfaz IEquatable<T> (cfr. Comparación de Igualdad en C# | Parte 3/5 | Protocolos de Igualdad Estándar: Método static object.ReferenceEquals e Interfaz IEquatable).

4. Sobrescritura de la Semántica de Igualdad

En general, estos son los pasos a seguir para la sobrescritura de la semántica de igualdad (Albahari J., 2012)
  1. Sobrescritura de los métodos GetHashCode() y Equals().
  2. Sobrecarga de los operadores != y ==. [Opcional]
  3. Implementación de la interfaz IEquatable<T>. [Opcional]

4.1 Sobrescritura del método GetHashCode()

El método virtual Object.GetHashCode() actúa como función hash predeterminada ("Object.GetHashCode Method", 2012). El valor entero devuelto por este método se usa para la inserción e identificación de un objeto en colecciones como IDictionary<TKey, TValue> o Hashtable.

Se mencionó que este método identifica un objeto en una colección, por lo tanto si el programador sobrescribe el método Equals, GetHashCode debería ser sobrescrito también. Estas son otras reglas a tener en cuenta a la hora de sobrescribir este método (Albahari J., 2012)
  • Debe retornar el mismo valor para dos objetos que retornan True en el método Equals.
  • No debe generar excepciones.
  • Siempre debe retornar el mismo valor para un objeto específico mientras que el estado de éste no cambie.
Además, para optimizar el rendimiento de una colección, GetHashCode debe crear el mayor espacio de identificadores únicos para evitar colisiones.

En Albhari J. (2012) se advierte 
Código hash en campo inmutables
Figura 1. Código hash en campo inmutables (Albahari J., 2012).
Esto quiere decir que la implementación personalizada de GetHashCode debería considerar el cálculo del código hash basado en campos inmutables.

4.2 Sobrescritura del método Equals

El método Object.Equals está basado en estos axiomas (Albahari J., 2012)
  1. La igualdad es reflexiva: un objeto debe ser igual así mismo.
  2. La igualdad es conmutativa: si x.Equals(y), entonces y.Equals(x).
  3. La igualdad es transitiva: si x.Equals(y) y y.Equals(z), entonces x.Equals(z).
  4. La operación de igualdad no debe generar excepciones; debe ser repetible y confiable.
  5. Un objeto no puede ser igual a null; siempre que no sea tipo nullable (cfr. Tipos Anulables (Nullable) en C#).

5. Conclusiones

Se estudiaron los básicos para cambiar el significado de la semántica de igualdad. Comprender este tipo de conceptos va a facilitar la implementación de semánticas de igualdad personalizadas para los tipos de datos creados por el propio programador en un contexto que resulte indispensable.

La última parte de esta serie describe otros temas relacionados con la igualdad y tipos datos personalizados: sobrescritura de los operadores == y !=, implementación de IEquatable<T>.

6. Literatura & Enlaces

Albahari, J., Albahari, B. (2012). C# 5.0 in a Nutshell. United States: O'Reilly Media.
C# Equality and Comparisons (2016, julio 28). Recuperado desde: https://app.pluralsight.com/library/courses/csharp-equality-comparisons
Receta No. 3-12 en C#: Crear un Objeto con Reflection (2016, julio 28). Recuperado desde: https://ortizol.blogspot.com/2014/06/receta-no-3-12-en-c-crear-un-objeto-con-reflection.html
Comparación de Igualdad en C# | Parte 3/5 | Protocolos de Igualdad Estándar: Método static object.ReferenceEquals e Interfaz IEquatable (2016, julio 28). Recuperado desde: https://ortizol.blogspot.com/2016/07/comparacion-de-igualdad-en-csharp-parte-3-5-protocolos-de-igualdad-estandar-metodo-static-object.referenceequals-e-interfaz-iequatable-t.html
Object.GetHashCode Method (System) (2016, julio 28). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx
Tipos Anulables (Nullable) en C# (2016, julio 28). Recuperado desde: https://ortizol.blogspot.com.co/2014/09/tipos-anulables-nullable-en-csharp.html


O

No hay comentarios:

Publicar un comentario

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