Pergunta

Fundo

Eu estou usando programação baseada em interface em um projeto atual e ter executado em um problema quando sobrecarga de operadores (especificamente os operadores de igualdade e desigualdade).


Pressupostos

  • Eu estou usando C # 3.0, .NET 3.5 e Visual Studio 2008

Atualização - A sequência de Assunção era falsa

  • A exigência de todas as comparações ao uso Igual ao invés de operador == não é uma solução viável, especialmente quando passar os seus tipos de bibliotecas (tais como coleções).

A razão que eu estava preocupado com exigindo Equals para ser usado em vez de operador == é que eu não poderia encontrar em qualquer lugar nas orientações .NET que afirmou que usaria Equals em vez de operador == ou mesmo sugerir isso. No entanto, após re-leitura Diretrizes para Overriding iguais e operador == I ter encontrado o seguinte:

Por padrão, os testes o operador == para a igualdade de referência por determinar se duas referências indicam o mesmo objeto. Portanto, tipos de referência não tem que implementar operador == a fim de obter essa funcionalidade. Quando um tipo é imutável, isto é, os dados que estão contidos no exemplo não pode ser alterado, a sobrecarga operador == para comparar igualdade valor em vez de igualdade de referência pode ser porque útil, como objectos imutáveis, eles podem ser considerados da mesma, desde como eles têm o mesmo valor. Não é uma boa idéia para operador override == em tipos não imutáveis.

Equatable interface

A interface IEquatable é usado por coleção genérica objetos como dicionário, lista e LinkedList ao testar pela igualdade em tais métodos como Contém, IndexOf, LastIndexOf, e remover. Deverá ser aplicada para qualquer objeto que pode ser armazenado em uma coleção genérica.


contraints

  • Qualquer solução não deve exigir lançando os objetos de suas interfaces com seus tipos de concreto.

Problema

  • Sempre que ambos os lados do operador == é uma interface, nenhum operador == sobrecarga de método assinatura dos tipos concretos subjacentes irá corresponder e, assim, o operador de objeto padrão == método será chamado.
  • Quando um operador sobrecarga sobre uma classe, pelo menos um dos parâmetros do operador binário deve ser do tipo que contém, de outro modo um erro do compilador é gerado (BC33021 Erro http://msdn.microsoft.com/en-us/library/watt39ff.aspx )
  • Não é possível especificar a implementação de uma interface

Veja Código e saída abaixo demonstra o problema.


Pergunta

Como você fornecer sobrecargas de operador adequados para suas classes ao utilizar programação interface de base?


Referências

== Operador (Referência C #)

Para tipos de valores pré-definidos, o operador de igualdade (==) retorna true se os valores de seus operadores são iguais, caso contrário false. Para além seqüência de tipos de referência, == retorna true se seus dois operandos se referir ao mesmo objeto. Para o tipo de corda, == compara os valores das strings.


Consulte também


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

saída

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

Solução

A resposta curta: Eu acho que a segunda hipótese pode ser falho. Equals() é o caminho certo para verificar se há igualdade semântica de dois objetos, não operator ==.


Long resposta:. Resolução de sobrecarga para os operadores é tempo realizado em tempo de compilação, não executar

A menos que o compilador pode definitivamente conhecer os tipos de objetos que ele está aplicando um operador, não vai compilar. Desde o compilador não pode ter certeza que um IAddress vai ser algo que tem uma substituição para == definida, ela cai de volta para a implementação operator == padrão de System.Object.

Para ver isso mais claramente, tente definir uma operator + para Address e adicionando duas instâncias IAddress. A menos que você explicitamente convertido para Address, ele vai deixar de compilar. Por quê? Porque o compilador não pode dizer que uma IAddress particular é um Address, e não há nenhuma implementação operator + padrão para voltar a cair no System.Object.


A parte de sua frustração, provavelmente, decorre do fato de que implementos Object um operator ==, e tudo é uma Object, então o compilador pode resolver com êxito operações como a == b para todos os tipos. Quando você == cancelou, você esperava ver o mesmo comportamento, mas não o fez, e isso é porque o melhor combinam com o compilador pode encontrar é a implementação Object originais.

A exigência de todas as comparações ao uso Igual ao invés de operador == não é uma solução viável, especialmente quando passar os seus tipos de bibliotecas (tais como coleções).

Na minha opinião, este é exatamente o que você deve fazer. Equals() é o caminho certo para verificar se há igualdade semântica de dois objetos. Às vezes a igualdade semântica é apenas igualdade de referência, caso em que você não vai precisar mudar nada. Em outros casos, como no seu exemplo, você vai substituir Equals quando você precisa de um contrato de igualdade forte do que a igualdade de referência. Por exemplo, você pode querer considerar dois Persons iguais se eles têm o mesmo número de Segurança Social, ou dois Vehicles iguais se eles têm o mesmo VIN.

Mas Equals() e operator == não são a mesma coisa. Sempre que você precisar operator == substituição, você deve substituir Equals(), mas quase nunca o contrário. operator == é mais uma conveniência sintática. Algumas linguagens CLR (por exemplo Visual Basic.NET) nem sequer permitem-lhe substituir o operador de igualdade.

Outras dicas

Nós correu para o mesmo problema, e encontrou uma excelente solução:. Padrões personalizados ReSharper

Nós configuramos todos os nossos usuários para usar um catálogo padrão global comum para além da sua própria, e colocou-o no SVN para que ele possa ser versionadas e atualizado para todos.

O catálogo inclui todos os padrões conhecidos de estar errado em nosso sistema:

$i1$ == $i2$ (onde I1 e I2 são expressões do nosso tipo de interface, ou derivados.

o padrão substituir é

$i1$.Equals($i2$)

e da gravidade é "Show de erro".

Da mesma forma que temos $i1$ != $i2$

Espero que isso ajude. P. S. catálogos globais é a característica em ReSharper 6.1 (EAP), será marcado como final muito em breve.

Atualizar : Eu entrou com uma ReSharper Issue a marca todos interface '==' um aviso a menos que ele está comparando a nulo. Por favor vote se você acha que é uma característica digna.

Update2 :. ReSharper também tem [CannotApplyEqualityOperator] atributo que pode ajudar

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top