Pergunta

me deparei com isso recentemente, até agora tenho sido feliz substituindo o operador de igualdade ( == ) e / ou Igual método, a fim de ver se duas referências tipos realmente continha as mesmas dados (ou seja, duas instâncias diferentes que têm a mesma aparência).

Eu tenho usado isso ainda mais desde que eu fui ficando cada vez mais para testes automatizados (referência comparando / dados esperados contra que voltou).

Ao olhar sobre algumas das orientações padrões codificação em MSDN me deparei com um artigo que aconselha contra ela. Agora eu entendo por o artigo está dizendo isso (porque eles não são os mesmos exemplo ), mas ele não responder à pergunta:

  1. O que é a melhor maneira de comparar dois tipos de referência?
  2. Devemos implementar IComparable ? (Eu vi também mencionar que este deve ser reservada apenas para os tipos de valor).
  3. Existe alguma interface Eu não sei sobre?
  4. Será que devemos apenas rolar a nossa própria?!

Muito obrigado ^ _ ^

Atualização

Parece que eu tinha mis-ler alguns dos documentos (que tem sido um longo dia) e substituindo Igual pode ser o caminho a percorrer ..

Se você estiver implementando referência tipos, você deve considerar prioritária o método iguala em um tipo de referência Se o seu tipo parece um tipo de base tais como Point, String, numerogrante, e assim por diante. A maioria dos tipos de referência deve Não sobrecarregue a igualdade operador, mesmo se substituir Equals . Contudo, se você estiver implementando uma referência tipo que se destina a ter valor semântica, tais como um número complexo Tipo, você deve substituir o da igualdade operador.

Foi útil?

Solução

Parece que você está programando em C #, que tem um método chamado Equals que sua classe deve implementar, você deve querer comparar dois objetos usando alguma outra métrica que "são estes dois ponteiros (porque alças objeto são apenas isso, ponteiros) para o mesmo endereço de memória?".

Eu agarrei um código de exemplo de aqui :

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Java tem mecanismos muito semelhantes. O equals () método é parte dos objeto classe e seus sobrecargas classe que se você quer este tipo de funcionalidade.

A razão sobrecarregar '==' pode ser uma má idéia para objetos é que, geralmente, você ainda quer ser capaz de fazer as "são estes o mesmo ponteiro" comparações. Estes são geralmente invocada para, por exemplo, a inserção de um elemento em uma lista onde são permitidos sem duplicatas, e algumas de suas coisas quadro pode não funcionar se este operador está sobrecarregado de uma forma não-padrão.

Outras dicas

igualdade de execução em .NET corretamente, de forma eficiente e sem código de duplicação é difícil. Especificamente, para os tipos de referência com semântica de valor (isto é, tipos imutáveis ??que equvialence tratar como igualdade ), você deve implementar o System.IEquatable<T> interface de , e você deve implementar todas as diferentes operações (Equals, GetHashCode e ==, !=).

Como um exemplo, aqui está uma igualdade de classe de valor de execução:

class Point : IEquatable<Point> {
    public int X { get; }
    public int Y { get; }

    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public bool Equals(Point other) {
        if (other is null) return false;
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj) => Equals(obj as Point);

    public static bool operator ==(Point lhs, Point rhs) => object.Equals(lhs, rhs);

    public static bool operator !=(Point lhs, Point rhs) => ! (lhs == rhs);

    public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode();
}

As únicas partes móveis no código acima são as partes em negrito: a segunda linha em Equals(Point other) e o método GetHashCode(). O outro código deve permanecer inalterado.

Para as classes de referência que não representam valores imutáveis, não implementam o == operadores e !=. Em vez disso, use seu significado padrão, que é comparar a identidade do objeto.

O código intencionalmente equivale mesmo objetos de um tipo de classe derivada. Muitas vezes, isso pode não ser desejável porque a igualdade entre a classe base e classes derivadas não está bem definido. Infelizmente, .NET e as diretrizes de codificação não são muito claras aqui. O código que ReSharper cria, postou em outra resposta , é suscetível ao comportamento indesejável em tais casos porque Equals(object x) e Equals(SecurableResourcePermission x) < em> irá tratar neste caso de forma diferente.

Para alterar esse comportamento, uma verificação de tipo adicional tem de ser inserido no método Equals fortemente tipado acima:

public bool Equals(Point other) {
    if (other is null) return false;
    if (other.GetType() != GetType()) return false;
    return X.Equals(other.X) && Y.Equals(other.Y);
}

Abaixo tenho resumiu o que você precisa fazer ao implementar IEquatable e desde a justificação das várias páginas de documentação do MSDN.


Resumo

  • Ao testar a igualdade valor é desejado (como ao usar objetos em coleções), você deve implementar a interface IEquatable, Object.Equals substituição, e GetHashCode para sua classe.
  • Ao testar para igualdade de referência é desejada você deve usar o operador ==, operador! = E Object.ReferenceEquals .
  • Você só deve substituir operador == eo operador! = Para valuetypes e referência imutável tipos.

Justificação

IEquatable

A interface System.IEquatable é usada para comparar duas instâncias de um objeto para a igualdade. Os objetos são comparados com base na lógica implementado na classe. A comparação resulta em um valor booleano que indica se os objetos são diferentes. Isto está em contraste com a interface System.IComparable, que devolver um número inteiro que indica como os valores de objectos são diferentes.

A interface IEquatable declara dois métodos que devem ser substituídos. O método Equals contém a implementação para realizar a comparação real e retornar true se os valores de objetos são iguais, ou falso, se eles não são. O método GetHashCode deve devolver um valor de hash único que pode ser usado para identificar objectos idênticos que contêm valores diferentes. O tipo de algoritmo de hash usado é específico da implementação.

IEquatable.Equals Método

  • Você deve implementar IEquatable para seus objetos para lidar com a possibilidade de que eles serão armazenados em uma matriz ou coleção genérica.
  • Se você implementar IEquatable você também deve substituir as implementações de classe base de Object.Equals (Object) e GetHashCode para que o seu comportamento é consistente com o do método IEquatable.Equals

Diretrizes para Overriding Equals () e operador == (C # Guia de programação)

  • x.Equals (x) retorna true.
  • x.Equals (y) retorna o mesmo valor como y.equals (x)
  • se (x.Equals (y) && y.equals (z)) devolve verdadeiro, então x.Equals (z) devolve verdadeiro.
  • invocações sucessivas de x. Equals (y) retornar o mesmo valor, desde que os objetos referenciados pelo x e y não são modificados.
  • x. Equals (null) retorna tipos de valores falsos (para não-nulo somente. Para mais informações, consulte tipos anuláveis ??(C # guia de programação) .)
  • A nova implementação de Equals não deve lançar exceções.
  • É recomendável que qualquer classe que substitui também é igual a substituir Object.GetHashCode.
  • Está é recomendado que, além de Equals execução (objeto), qualquer classe também implementar Equals (tipo) de seu próprio tipo, para melhorar o desempenho.

Por padrão, o operador == testes de 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 na instância não pode ser alterado, sobrecarregando operador == para comparar o valor equalidade em vez de igualdade de referência pode ser útil porque, como objetos imutáveis, eles podem ser considerados o mesmo, enquanto eles têm o mesmo valor. Não é uma boa idéia para operador override == em tipos não imutáveis.

  • operador sobrecarregado == implementações não deve lançar exceções.
  • Qualquer tipo que sobrecargas operador == também deve sobrecarregar o operador! =.

== Operador (Referência C #)

  • Para tipos predefinidos valor, o operador de igualdade (==) retorna true se os valores de seus operadores são iguais, false caso contrário.
  • 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.
  • Quando teste para NULL usando == comparações dentro do seu operador == substituições, certifique-se de usar o operador base de classe de objeto. Se não o fizer, recursão infinita irá ocorrer, resultando em um stackoverflow.

Object.Equals Método (Object)

Se a sua programação operador suportes de linguagem sobrecarga e se você optar por sobrecarregar o operador de igualdade para um determinado tipo, que tipo deve substituir o método Equals. Tais implementações do método Equals deve retornar os mesmos resultados que o operador de igualdade

As seguintes diretrizes são para implementar um tipo de valor :

  • Considere substituir Equals para obter um melhor desempenho ao longo do que a prevista pela implementação padrão de Iguais no ValueType.
  • Se você substituir Equals ea sobrecarga de operador suporte de idiomas, você deve sobrecarregar o operador de igualdade para o seu tipo de valor.

As seguintes diretrizes são para a implementação de um tipo de referência :

  • Considere substituindo Equals em um tipo de referência, se a semântica do tipo são baseadas no fato de que o tipo representa cerca de valor (es).
  • A maioria dos tipos de referência não deve sobrecarregar o operador de igualdade, mesmo que substituir Equals. No entanto, se você estiver implementando um tipo de referência que se destina a ter semântica de valor, como um tipo de número complexo, você deve substituir o operador de igualdade.

adicionais Gotchas

  • Ao substituir GetHashCode () Certifique-se de tipos de referência de teste para NULL antes de usá-los no código hash.
  • eu tive um problema com programação baseada em interface e sobrecarga de operador descrito aqui: Operador de sobrecarga com interface baseada em C #

Esse artigo apenas recomenda a substituir o operador de igualdade (para tipos de referência), e não contra substituindo Equals. Você deve substituir Equals dentro de seu objeto (de referência ou valor) se verificações de igualdade significa algo mais do que verificações de referência. Se você quiser uma interface, você também pode implementar IEquatable (usado por coleções genéricas). Se você implementar IEquatable, no entanto, você também deve substituir iguais, como o IEquatable observa estados seção:

Se você implementar IEquatable , você também deve substituir as implementações de classe base de Object.Equals (Object) e GetHashCode para que o seu comportamento é consistente com o do método IEquatable .Equals. Se você fizer Object.Equals substituição (Object), sua implementação substituído também é chamado em chamadas para os Iguais estáticos (System.Object, System.Object) método em sua classe. Isso garante que todas as invocações do método Equals retorno resultados consistentes.

No que diz respeito a saber se você deve implementar Equals e / ou o operador de igualdade:

A partir Implementação do Método Equals

A maioria dos tipos de referência não deve sobrecarregar o operador de igualdade, mesmo que substituir Equals.

A partir diretrizes para a implementação iguais e o operador de igualdade ( ==)

Substitua o método sempre que você implementar o operador de igualdade (==), e torná-los fazer a mesma coisa iguais.

Isso só diz que você precisa para substituir Equals sempre que você implementar o operador de igualdade. Ele faz não dizer que você precisa para substituir o operador de igualdade quando você substituir Equals.

Para objetos complexos que irão produzir comparações específicas, em seguida, execução IComparable e que definem a comparação nos métodos Comparar É uma boa implementação.

Por exemplo, temos "veículo" objetos onde a única diferença pode ser o número de registo e usamos isso para comparar para garantir que o valor esperado retornou em testes é o que nós queremos.

Eu costumo usar o ReSharper automaticamente faz. por exemplo, ele autocreated este para um dos meus tipos de referência:

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.GetType() == typeof(SecurableResourcePermission) && Equals((SecurableResourcePermission)obj);
}

public bool Equals(SecurableResourcePermission obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.ResourceUid == ResourceUid && Equals(obj.ActionCode, ActionCode) && Equals(obj.AllowDeny, AllowDeny);
}

public override int GetHashCode()
{
    unchecked
    {
        int result = (int)ResourceUid;
        result = (result * 397) ^ (ActionCode != null ? ActionCode.GetHashCode() : 0);
        result = (result * 397) ^ AllowDeny.GetHashCode();
        return result;
    }
}

Se você quiser substituir == e ainda fazer verificações de referência, você ainda pode usar Object.ReferenceEquals.

Microsoft parece ter mudado sua música, ou pelo menos há conflitantes informações sobre não sobrecarregar o operador de igualdade. De acordo com esta Microsoft artigo intitulado Como definir Valor Igualdade para uma tipo:

"O == e! = Operadores podem ser usados ??com aulas mesmo se a classe não sobrecarregá-los. No entanto, o comportamento padrão é para executar uma verificação de igualdade de referência. Em uma classe, se você sobrecarregar o método Equals, você deve sobrecarregar o == e! = operadores, mas não é necessário. "

De acordo com Eric Lippert em sua resposta a uma pergunta eu perguntei sobre código mínimo para a igualdade no C # - ele diz:

"O perigo que você correr para aqui é que você começa um operador == definido para você que faz igualdade de referência por padrão. Você poderia facilmente acabar em uma situação onde um método Equals sobrecarregado faz igualdade de valor e == faz igualdade de referência , e então você acidentalmente usar igualdade de referência em coisas-não-referência iguais que são de valor igual. Esta é uma prática passível de erro que é difícil de detectar por revisão de código humana.

Um par de anos atrás eu trabalhava em um algoritmo de análise estática para detectar estatisticamente esta situação, e encontramos uma taxa de defeito de cerca de dois casos por milhão de linhas de código em todas as bases de código que estudamos. Ao considerar apenas a bases de código que tinha algum lugar substituídas Equals, a taxa de defeito era obviamente consideravelmente maior!

Além disso, considerar os custos versus os riscos. Se você já tem implementações de IComparable em seguida, escrever todos os operadores é one-liners triviais que não terão erros e nunca serão alterados. É o código mais barato que você está indo cada vez para escrever. Se dada a escolha entre o custo fixo de escrever e testar uma dúzia de métodos minúsculos vs o custo ilimitada de encontrar e corrigir um disco-para-ver bug onde a igualdade de referência é usada em vez da igualdade de valor, eu sei qual eu escolheria."

O .NET Framework nunca vai usar == ou! = Com qualquer tipo que você escreve. Mas, o perigo é o que aconteceria se alguém o faça. Então, se a classe é para uma festa de 3, então eu sempre fornecer a == e! = Operadores. Se a classe se destina apenas a ser utilizado internamente pelo grupo, eu ainda seria provavelmente implementar o == e! = Operadores.

Eu só iria implementar o <, <=,>, e> = operadores se IComparable foi implementado. IComparable só deve ser implementado se o tipo de necessidades a ordenação apoio -. Como ao ordenar ou sendo usado em um ordenado recipiente genérico como SortedSet

Se o grupo ou empresa tinha uma política para nunca implementar o == e = operadores - então eu iria, claro, seguir essa política. Se tal política estavam no local, então seria sábio para aplicá-la com uma ferramenta de análise de código Q / A que sinaliza qualquer ocorrência do == e! = Operadores quando utilizado com um tipo de referência.

Acredito que a obtenção de algo tão simples como verificar objetos de igualdade correta é um pouco complicado com design do .NET.

Para Struct

1) Implementar IEquatable<T>. Ele melhora o desempenho visivelmente.

2) Uma vez que você está tendo o seu próprio Equals agora, GetHashCode substituição, e para ser coerente com vários igualdade verificar override object.Equals também.

3) sobrecarga de operadores == e != não precisam ser religiosamente feito desde o compilador irá avisá se equiparar involuntariamente uma estrutura com um outro com um == ou !=, mas é bom para fazê-lo para ser consistente com métodos Equals.

public struct Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;

        return Equals((Entity)obj);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Para Class

A partir MS:

A maioria dos tipos de referência não deve sobrecarregar o operador de igualdade, mesmo que substituir Equals.

Para mim == sente como a igualdade de valor, mais como um açúcar sintático para o método Equals. Escrevendo a == b é muito mais intuitivo do que escrever a.Equals(b). Raramente vamos precisar verificar igualdade de referência. Em níveis abstratos que lidam com representações lógicas de objetos físicos isso não é algo que seria necessário verificar. Eu acho que ter diferentes semântica para == e Equals pode realmente ser confuso. Eu acredito que deve ter sido == para a igualdade de valor e Equals para referência (ou um nome melhor como IsSameAs) igualdade no primeiro lugar. eu gostaria de não ter MS diretriz a sério aqui, não apenas porque não é natural para mim, mas também porque a sobrecarga == não faz qualquer dano maior. que é diferente de não imperativas não-genérico Equals ou GetHashCode que pode morder de volta, porque quadro não usa == qualquer lugar, mas somente se nós mesmos usá-lo. O que eu ganho real só beneficiar de não sobrecarga == e != será a coerência com a concepção de toda a estrutura sobre a qual não tenho controle de. E isso é realmente uma grande coisa, tão tristemente eu vou cumpri-lo .

Com semântica de referência (objetos mutáveis)

1) Equals Override e GetHashCode.

2) IEquatable<T> de execução não é uma obrigação, mas vai ser bom se você tiver um.

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

com valores semânticos (objetos imutáveis)

Esta é a parte complicada. Pode obter facilmente confuso se não tomar cuidado ..

1) Equals Override e GetHashCode.

2) == Sobrecarga e != de jogo Equals. Certifique-se de que ele funciona para valores nulos .

2) IEquatable<T> de execução não é uma obrigação, mas vai ser bom se você tiver um.

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        if (ReferenceEquals(e1, null))
            return ReferenceEquals(e2, null);

        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Tome especial cuidado para ver como ele deve tarifa se sua classe pode ser herdada, em tais casos, você terá que determinar se um objeto de classe base pode ser igual a um objeto classe derivada. Idealmente, se nenhum objeto da classe derivada é usada para verificação de igualdade, em seguida, uma instância da classe base pode ser igual a uma instância de classe derivada e, nesses casos, não há necessidade de verificar a igualdade Type em Equals genérico de classe base.

Em cuidar geral, não ao código duplicado. Eu poderia ter feito uma classe base abstrata genérico (IEqualizable<T> ou assim) como um modelo para permitir a re-utilização mais fácil, mas infelizmente em C # que me impede de decorrentes de classes adicionais.

Todas as respostas acima não consideram polimorfismo, muitas vezes você quer referências para usar o Equals derivados derivada mesmo quando comparado através de uma referência base. Por favor, veja as perguntas / discussão / respostas aqui - Igualdade e polimorfismo

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