Pregunta

Fondo

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


Suposiciones

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

ACTUALIZACIÓN: ¡La siguiente suposición era falsa!

  • Exigir que todas las comparaciones utilicen Equals en lugar de operator== no es una solución viable, especialmente cuando se pasan sus tipos a bibliotecas (como Colecciones).

La razón por la que me preocupaba exigir el uso de Equals en lugar de operator== es que no pude encontrar en ninguna parte de las pautas de .NET que indicara que usaría Equals en lugar de operator== o incluso sugerirlo.Sin embargo, después de volver a leer Directrices para anular iguales y operadores == He encontrado esto:

De forma predeterminada, el operador == prueba la igualdad de referencias determinando si dos referencias indican el mismo objeto.Por lo tanto, los tipos de referencia no tienen que implementar el operador == para obtener esta funcionalidad.Cuando un tipo es inmutable, es decir, los datos contenidos en la instancia no se pueden cambiar, sobrecargar el operador == para comparar la igualdad de valores en lugar de la igualdad de referencia puede ser útil porque, como objetos inmutables, pueden considerarse iguales siempre y cuando ya que tienen el mismo valor.No es una buena idea anular el operador == en tipos no inmutables.

y esto Interfaz equiparable

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 Contiene, IndexOf, LastIndexOf y Remove.Debe implementarse para cualquier objeto que pueda almacenarse en una colección genérica.


Contracciones

  • Cualquier solución no debe requerir convertir los objetos desde sus interfaces a sus tipos concretos.

Problema

  • Siempre que ambos lados de operator== sean una interfaz, ninguna firma del método de sobrecarga operator== de los tipos concretos subyacentes coincidirá y, por lo tanto, se llamará al método predeterminado Object operator==.
  • Al sobrecargar un operador en una clase, al menos uno de los parámetros del operador binario debe ser el tipo contenedor; 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.

Consulte el código y el resultado a continuación para demostrar el problema.


Pregunta

¿Cómo proporciona sobrecargas de operadores adecuadas para sus clases cuando utiliza programación basada en interfaz?


Referencias

== Operador (Referencia C#)

Para tipos de valores predefinidos, el operador de igualdad (==) devuelve verdadero si los valores de sus operandos son iguales, falso en caso contrario.Para tipos de referencia distintos de cadena, == devuelve verdadero si sus dos operandos se refieren al mismo objeto.Para el tipo de cadena, == compara los valores de las cadenas.


Ver 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.");
            }
        }
    }
}

Producción

Address operator== overload called
Equal with both sides cast.
¿Fue útil?

Solución

Respuesta corta: Creo que su segunda hipótesis puede ser errónea. Equals() es el camino correcto para comprobar si hay igualdad semántica de dos objetos, no operator ==.


Respuesta larga:. Resolución de sobrecarga para los operadores es realizado en tiempo de compilación, no el tiempo de ejecución

A menos que el compilador puede saber definitivamente los tipos de los objetos se aplican a un operador, no va a compilar. Dado que el compilador no puede estar seguro de que un IAddress va a ser algo que tiene un reemplazo para == definido, cae de nuevo a la aplicación por defecto de operator == System.Object.

Para ver esto más claramente, trata de definir un operator + para Address y añadiendo dos instancias IAddress. A menos que se convierta explícitamente a Address, se producirá un error al compilar. ¿Por qué? Debido a que el compilador no puede decir que un IAddress particular es un Address, y no hay ninguna aplicación operator + por defecto para caer de nuevo en el System.Object.


Una parte de su frustración, probablemente se debe al hecho de que Object implementa un operator ==, y todo es un Object, por lo que el compilador puede resolver con éxito las operaciones como a == b para todos los tipos. Cuando sustituimos ==, que esperaba ver el mismo comportamiento, pero no lo hizo, y eso es debido a que el mejor partido que el compilador puede encontrar es la implementación Object originales.

  

Exigir que todas las comparaciones con el uso no es igual en vez de operador == es una solución viable, especialmente al pasar sus tipos de bibliotecas (tales como colecciones).

En mi opinión, esto es precisamente lo que debería estar haciendo. Equals() es el camino correcto para comprobar si hay igualdad semántica de dos objetos. la igualdad A veces es sólo semántica referencia igualdad, en cuyo caso no será necesario cambiar nada. En otros casos, como en el ejemplo, podrás anular Equals cuando se necesita un contrato de igualdad más fuerte que la igualdad de referencia. Por ejemplo, es posible que desee considerar dos Persons iguales si tienen el mismo número de la Seguridad Social, o dos Vehicles iguales si tienen el mismo VIN.

Pero Equals() y operator == no son la misma cosa. Siempre que necesite para anular operator ==, debe reemplazar Equals(), pero casi nunca a la inversa. operator == es más una conveniencia sintáctica. Algunos lenguajes CLR (por ejemplo, Visual Basic.NET) ni siquiera le permiten anular el operador de igualdad.

Otros consejos

Nos encontramos con el mismo problema y encontramos una excelente solución:Resuelve patrones personalizados.

Configuramos a TODOS nuestros usuarios para que utilicen 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 que se sabe que son incorrectos en nuestro sistema:

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

el patrón de reemplazo es

$i1$.Equals($i2$)

y la gravedad es "Mostrar como error".

De manera similar tenemos $i1$ != $i2$

Espero que esto ayude.PDLos catálogos globales son la característica de Resharper 6.1 (EAP) y se marcarán como finales muy pronto.

Actualizar:presenté un Problema con el afilador para marcar toda la interfaz '==' como advertencia a menos que se compare con nula.Vote si cree que es una característica valiosa.

Actualización2:Resharper también tiene el atributo [CannotApplyEqualityOperator] que puede ayudar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top