Sobrecarga del Operador con Programación Basada en Interfaz en C#


Antecedentes

Estoy usando programación basada en interfaz en un proyecto actual y me he encontrado con un problema al sobrecargar operadores (específicamente los operadores de Igualdad y Desigualdad).


Supuestos

  • Estoy usando C # 3.0,. NET 3.5 y Visual Studio 2008

ACTUALIZACIÓN - La Siguiente Suposición era Falsa!

  • Requerir que todas las comparaciones usen Igual en lugar de operador= = no es una solución viable, especialmente al pasar su tipos a bibliotecas (como Colecciones).

La razón por la que me preocupaba requerir que se usaran Iguales en lugar de operator== es que no pude encontrar en ninguna parte de las directrices de.NET que indicara que usaría Iguales en lugar de operator== o incluso sugerirlo. Sin embargo, después de releer Las Directrices para Sobreescribir Equals y Operator = = he encontrado esto:

Por defecto, el operador = = prueba la igualdad de referencia determinando si dos referencias indica el mismo objeto. Por lo tanto, los tipos de referencia no tienen que implementar operator == para obtener esta funcionalidad. Cuando un tipo es inmutable, es decir, los datos que contiene la instancia no se pueden cambiar, operador de sobrecarga == para comparar la igualdad de valor en lugar de la igualdad de referencia puede ser útil porque, como objetos inmutables, se pueden considerar los mismos siempre y cuando tengan el mismo valor. No es una buena idea anular operator = = in non-immutable tipo.

Y esta Interfaz igualable

La interfaz IEquatable es utilizada por objetos de colección genéricos como Dictionary, List y LinkedList cuando se prueba la igualdad en métodos como Contains, indexOf, lastIndexOf y Remove. Debe implementarse para cualquier objeto que pueda almacenarse en una colección genérica.


Contraindicaciones

  • Cualquier solución no debe requerir la fundición de los objetos de sus interfaces a su tipos de hormigón.

Problema

  • Cuando ambos lados del operador== son una interfaz, ningún operador== la firma del método de sobrecarga de los tipos de concreto subyacentes coincidirá y, por lo tanto, se llamará al método operador de objeto predeterminado==.
  • Cuando se sobrecarga un operador en una clase, al menos uno de los parámetros del operador binario debe ser el tipo que contiene, de lo contrario se genera un error del compilador (Error BC33021 http://msdn.microsoft.com/en-us/library/watt39ff.aspx )
  • No es posible especificar la implementación en una interfaz

Vea el Código y la salida a continuación demostrando el problema.


Pregunta

¿Cómo proporciona sobrecargas de operador adecuadas para sus clases cuando utiliza la programación de base de interfaz?


Referencias

== Operador (Referencia C#)

Para los tipos de valores predefinidos, el operador de igualdad ( = = ) devuelve true si los valores de sus operandos son iguales, false si no. Para tipos de referencia distintos de string, == devuelve true si sus dos operandos se refieren al mismo objeto. Para el tipo de cadena, == compara los valores de las cadenas.


Véase también


Código

using System;

namespace OperatorOverloadsWithInterfaces
{
    public interface IAddress : IEquatable<IAddress>
    {
        string StreetName { get; set; }
        string City { get; set; }
        string State { get; set; }
    }

    public class Address : IAddress
    {
        private string _streetName;
        private string _city;
        private string _state;

        public Address(string city, string state, string streetName)
        {
            City = city;
            State = state;
            StreetName = streetName;
        }

        #region IAddress Members

        public virtual string StreetName
        {
            get { return _streetName; }
            set { _streetName = value; }
        }

        public virtual string City
        {
            get { return _city; }
            set { _city = value; }
        }

        public virtual string State
        {
            get { return _state; }
            set { _state = value; }
        }

        public static bool operator ==(Address lhs, Address rhs)
        {
            Console.WriteLine("Address operator== overload called.");
            // If both sides of the argument are the same instance or null, they are equal
            if (Object.ReferenceEquals(lhs, rhs))
            {
                return true;
            }

            return lhs.Equals(rhs);
        }

        public static bool operator !=(Address lhs, Address rhs)
        {
            return !(lhs == rhs);
        }

        public override bool Equals(object obj)
        {
            // Use 'as' rather than a cast to get a null rather an exception
            // if the object isn't convertible
            Address address = obj as Address;
            return this.Equals(address);
        }

        public override int GetHashCode()
        {
            string composite = StreetName + City + State;
            return composite.GetHashCode();
        }

        #endregion

        #region IEquatable<IAddress> Members

        public virtual bool Equals(IAddress other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return ((this.City == other.City)
                && (this.State == other.State)
                && (this.StreetName == other.StreetName));
        }

        #endregion
    }

    public class Program
    {
        static void Main(string[] args)
        {
            IAddress address1 = new Address("seattle", "washington", "Awesome St");
            IAddress address2 = new Address("seattle", "washington", "Awesome St");

            functionThatComparesAddresses(address1, address2);

            Console.Read();
        }

        public static void functionThatComparesAddresses(IAddress address1, IAddress address2)
        {
            if (address1 == address2)
            {
                Console.WriteLine("Equal with the interfaces.");
            }

            if ((Address)address1 == address2)
            {
                Console.WriteLine("Equal with Left-hand side cast.");
            }

            if (address1 == (Address)address2)
            {
                Console.WriteLine("Equal with Right-hand side cast.");
            }

            if ((Address)address1 == (Address)address2)
            {
                Console.WriteLine("Equal with both sides cast.");
            }
        }
    }
}

Salida

Address operator== overload called
Equal with both sides cast.
Author: Zach Burlingame, 2009-04-08

2 answers

Respuesta corta: Creo que su segunda suposición puede ser defectuosa. Equals() es la forma correcta de comprobar la igualdad semántica de dos objetos, no operator ==.


Respuesta larga: La resolución de sobrecarga para los operadores se realiza en tiempo de compilación, no en tiempo de ejecución.

A menos que el compilador pueda conocer definitivamente los tipos de objetos a los que está aplicando un operador, no compilará. Dado que el compilador no puede estar seguro de que un IAddress va a ser algo que tiene un override para == definido, vuelve a la implementación predeterminada operator == de System.Object.

Para ver esto más claramente, intente definir un operator + para Address y agregar dos instancias IAddress. A menos que se lance explícitamente a Address, fallará al compilar. ¿Por qué? Porque el compilador no puede decir que un IAddress particular es un Address, y no hay una implementación predeterminada operator + a la que recurrir en System.Object.


Parte de su frustración probablemente proviene del hecho que Object implementa un operator ==, y todo es un Object, por lo que el compilador puede resolver con éxito operaciones como a == b para todos los tipos. Cuando anulaste ==, esperabas ver el mismo comportamiento pero no lo hiciste, y eso es porque la mejor coincidencia que el compilador puede encontrar es la implementación original de Object.

Requerir que todas las comparaciones usen Equals en lugar de operator= = no es una solución viable, especialmente cuando pasa sus tipos a bibliotecas (como Colecciones).

En mi opinión, esto es precisamente lo que deberías estar haciendo. Equals() es la forma correcta de comprobar la igualdad semántica de dos objetos. A veces la igualdad semántica es solo igualdad de referencia, en cuyo caso no tendrá que cambiar nada. En otros casos, como en su ejemplo, anulará Equals cuando necesite un contrato de igualdad más fuerte que la igualdad de referencia. Por ejemplo, es posible que desee considerar dos Persons iguales si tienen lo mismo Número de Seguro Social, o dos Vehicles iguales si tienen el mismo VIN.

Pero Equals() y operator == no son lo mismo. Siempre que necesite anular operator ==, debe anular Equals(), pero casi nunca al revés. operator == es más una conveniencia sintáctica. Algunos lenguajes CLR (p. ej. Visual Basic.NET) ni siquiera le permite anular el operador de igualdad.

 55
Author: John Feminella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-08 04:28:24

Nos encontramos con el mismo problema, y encontramos una solución excelente: Patrones personalizados de Resharper.

Configuramos a TODOS nuestros usuarios para usar un catálogo de patrones global común además del suyo propio, y lo colocamos en SVN para que pueda ser versionado y actualizado para todos.

El catálogo incluía todos los patrones conocidos como incorrectos en nuestro sistema:

$i1$ == $i2$ (donde i1 e i2 son expresiones de nuestro tipo de interfaz, o derivadas.

El patrón de reemplazo es

$i1$.Equals($i2$)

Y la gravedad es "Mostrar como error".

Del mismo modo tenemos $i1$ != $i2$

Espero que esto ayude. P.d. Catálogos globales es la característica en Resharper 6.1 (EAP), será marcado como final muy pronto.

Update : Presenté un problema Resharper para marcar toda la interfaz '==' una advertencia a menos que se compare con null. Por favor, vote si cree que es una característica digna.

Update2 : Resharper también tiene [CannotApplyEqualityOperator] atributo que puede ayudar.

 4
Author: Yurik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-26 03:24:42