Operadores de igualdade - testam se dois objetos são iguais ou não

Os operadores == (igualdade) e != (desigualdade) verificam se os operandos são iguais ou não. Os tipos de valor são iguais quando seu conteúdo é igual. Os tipos de referência são iguais quando as duas variáveis se referem ao mesmo armazenamento.

A linguagem C# faz referência a documentos da versão mais recentemente lançada da linguagem C#. Ele também contém a documentação inicial para funcionalidades em pré-visualizações públicas para o próximo lançamento do idioma.

A documentação identifica qualquer recurso introduzido pela primeira vez nas três últimas versões do idioma ou nas versões prévias públicas atuais.

Dica

Para descobrir quando um recurso foi introduzido pela primeira vez em C#, consulte o artigo sobre o histórico de versão da linguagem C#.

Você pode usar o is operador de correspondência de padrões como uma alternativa a um == teste ao testar em relação a um valor constante. O is operador usa a semântica de igualdade padrão para todos os tipos de valor e referência.

Operador de igualdade ==

O operador de igualdade == retornará true se seus operandos forem iguais; caso contrário, false.

Igualdade de tipos de valor

Os operandos dos tipos de valor internos serão iguais se seus valores forem iguais:

int a = 1 + 2 + 3;
int b = 6;
Console.WriteLine(a == b);  // output: True

char c1 = 'a';
char c2 = 'A';
Console.WriteLine(c1 == c2);  // output: False
Console.WriteLine(c1 == char.ToLower(c2));  // output: True

Observação

Para os ==operadores , ,<> e <=>= , se qualquer um dos operandos não for um número (Double.NaN ou Single.NaN), o resultado da operação será false. Essa condição significa que o NaN valor não é maior que, menor ou igual a qualquer outro double valor (ou float) , incluindo NaN. Para obter mais informações e exemplos, consulte o artigo de referência Double.NaN ou Single.NaN.

Dois operandos do mesmo tipo de enum serão iguais se os valores correspondentes do tipo integral subjacente forem iguais.

Os tipos struct definidos pelo usuário não dão suporte ao operador ==, por padrão. Para dar suporte ao operador ==, um struct definido pelo usuário precisa sobrecarregá-lo.

As tuplas C# têm suporte interno para operadores == e operadores!=. Para obter mais informações, consulte a seção Igualdade de tupla do artigo Tipos de tupla.

Igualdade de tipos de referência

Por padrão, operandos de tipo de referência, excluindo registros, serão iguais se se referirem ao mesmo objeto:

public class ReferenceTypesEquality
{
    public class MyClass
    {
        private int id;

        public MyClass(int id) => this.id = id;
    }

    public static void Main()
    {
        var a = new MyClass(1);
        var b = new MyClass(1);
        var c = a;
        Console.WriteLine(a == b);  // output: False
        Console.WriteLine(a == c);  // output: True
    }
}

Como mostra o exemplo anterior, os tipos de referência definidos pelo usuário dão suporte ao == operador por padrão. No entanto, um tipo de referência pode sobrecarregar o operador ==. Se um tipo de referência sobrecarregar o operador ==, use o método Object.ReferenceEquals para verificar se as duas referências desse tipo dizem respeito ao mesmo objeto.

Igualdade de tipos de registro

Os tipos de registro dão suporte aos operadores == e != que, por padrão, fornecem semântica de igualdade de valor. Ou seja, dois operandos de registro são iguais quando ambos são null ou os valores correspondentes de todos os campos e as propriedades implementadas automaticamente são iguais.

public class RecordTypesEquality
{
    public record Point(int X, int Y, string Name);
    public record TaggedNumber(int Number, List<string> Tags);

    public static void Main()
    {
        var p1 = new Point(2, 3, "A");
        var p2 = new Point(1, 3, "B");
        var p3 = new Point(2, 3, "A");

        Console.WriteLine(p1 == p2);  // output: False
        Console.WriteLine(p1 == p3);  // output: True

        var n1 = new TaggedNumber(2, new List<string>() { "A" });
        var n2 = new TaggedNumber(2, new List<string>() { "A" });
        Console.WriteLine(n1 == n2);  // output: False
    }
}

Como mostra o exemplo anterior, a igualdade de membros do tipo de referência é comparada usando suas implementações de igualdade específicas.

Igualdade da cadeia de caracteres

Dois operandos da cadeia de caracteres serão iguais quando ambos forem null ou ambas as instâncias da cadeia de caracteres tiverem o mesmo comprimento e caracteres idênticos em cada posição de caractere:

string s1 = "hello!";
string s2 = "HeLLo!";
Console.WriteLine(s1 == s2.ToLower());  // output: True

string s3 = "Hello!";
Console.WriteLine(s1 == s3);  // output: False

Comparações de igualdade de cadeia de caracteres são comparações ordinais que diferenciam maiúsculas de minúsculas. Para obter mais informações sobre a comparação de cadeias de caracteres, confira Como comparar cadeias de caracteres no C#.

Igualdade de delegado

Dois operandos delegados do mesmo tipo de tempo de execução são iguais quando ambos são null ou suas listas de invocação têm o mesmo comprimento e têm entradas iguais em cada posição:

Action a = () => Console.WriteLine("a");

Action b = a + a;
Action c = a + a;
Console.WriteLine(object.ReferenceEquals(b, c));  // output: False
Console.WriteLine(b == c);  // output: True

Importante

Entradas iguais em uma lista de invocação incluem todos os parâmetros fixos na invocação, incluindo o receptor. O receptor é a instância de um objeto representado quando this a entrada é invocada.

var o1 = new object();
var o2 = new object();
var d1 = o1.ToString;
var d2 = o2.ToString;
Console.WriteLine(object.ReferenceEquals(d1, d2));  // output: False
Console.WriteLine(d1 == d2);  // output: False (different receivers)

Saiba mais na seção Operadores de igualdade de delegados na Especificação da linguagem C#.

Delegados provenientes da avaliação de expressões lambda semanticamente idênticas não são iguais, como mostra o exemplo a seguir:

Action a = () => Console.WriteLine("a");
Action b = () => Console.WriteLine("a");

Console.WriteLine(a == b);  // output: False
Console.WriteLine(a + b == a + b);  // output: True
Console.WriteLine(b + a == a + b);  // output: False

Operador de desigualdade !=

O operador != de desigualdade retornará true se seus operandos não forem iguais e false de outra forma. No caso dos operandos de tipos internos, a expressão x != y gera o mesmo resultado que a expressão !(x == y). Para obter mais informações sobre a igualdade de tipos, confira a seção Operador de igualdade.

O exemplo a seguir demonstra como usar o != operador:

int a = 1 + 1 + 2 + 3;
int b = 6;
Console.WriteLine(a != b);  // output: True

string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(s1 != s2);  // output: False

object o1 = 1;
object o2 = 1;
Console.WriteLine(o1 != o2);  // output: True

Igualdade nas hierarquias de classe

Os registros manipulam a herança corretamente sem trabalho manual. A igualdade gerada pelo compilador verifica o tipo de runtime e todas as propriedades declaradas, portanto, atende automaticamente aos requisitos de simetria e transitividade. Prefira record uma hierarquia não selada manual quando a igualdade de valor for a meta.

Importante

Use record sempre que possível – o compilador gera todos os membros de igualdade necessários para você. A implementação manual só é necessária quando seu tipo deve derivar de uma classe que não seja de registro ou tem outras restrições que impedem record.

Implemente a igualdade por conta própria quando um tipo não pode ser um registro

Aqui está uma implementação manual mínima para um tipo de valor que não pode ser um registro:

class Color : IEquatable<Color>
{
    public Color(int r, int g, int b)
    {
        R = r;
        G = g;
        B = b;
    }

    public int R { get; }
    public int G { get; }
    public int B { get; }

    public bool Equals(Color? other) =>
        other is not null && R == other.R && G == other.G && B == other.B;

    public override bool Equals(object? obj) => obj is Color other && Equals(other);
    public override int GetHashCode() => HashCode.Combine(R, G, B);
}

A implementação fornece três membros necessários: Equals(T?) como a comparação principal, override Equals(object?) para chamadas no nível do objeto e override GetHashCode() , portanto, as coleções baseadas em hash funcionam corretamente. HashCode.Combine é um auxiliar de biblioteca que cria um hash com base nos mesmos valores usados por Equals. IEquatable<T> Implementar (a Equals(T?) sobrecarga) é opcional, mas evita o boxe quando os chamadores já têm o tipo concreto.

Quando você também define == e !=, o idioma requer eles como um par; avisos CS0660 e CS0661 lembram você de manter todos os quatro membros consistentes.

Com os três membros acima em vigor, Equals reflete a igualdade de valor, mas == ainda testa a identidade porque nenhum == operador foi declarado ainda:

var red1 = new Color(255, 0, 0);
var red2 = new Color(255, 0, 0);

Console.WriteLine(red1.Equals(red2)); // => True
Console.WriteLine(red1 == red2);      // => False  (no == overload; identity check)

Uma implementação correta também deve satisfazer o contrato de equivalência (suponha xe yz não nulo):

  1. Reflexivo: x.Equals(x) retorna true.
  2. Simétrico: x.Equals(y) retorna o mesmo valor que y.Equals(x).
  3. Transitivo: se x.Equals(y) e y.Equals(z) ambos truesão, então x.Equals(z) deve ser true.
  4. Consistente: chamadas sucessivas para x.Equals(y) retornar o mesmo valor, desde que nenhum dos objetos mude.
  5. Comportamento nulo: x.Equals(null) retorna false; x.Equals(y) não deve ser gerado quando chamado em um não nulo x.

A igualdade de valor em uma hierarquia de classe não lacrada requer mais cuidado do que em uma classe lacrada para satisfazer as regras simétricas e transitivas. O perigo é que IEquatable<T>.Equals(T? other) a expedição segue o tipo declarado (o tipo escrito na declaração de variável) da variável, não seu tipo de runtime. Se Shape declarar um nãovirtualEquals(Shape? other), uma variável digitada como Shape que contém um Circle em runtime invoca — ignorando Circlesilenciosamente campos específicosShape.Equals. Dois Circle objetos com raios diferentes podem ser comparados como iguais quando acessados por meio de uma Shape variável.

O padrão correto requer dois requisitos de cooperação: tornar o método virtual tipado para que cada classe derivada Equals possa estender a comparação e adicionar um GetType() == other.GetType() proteção na implementação da classe base para que objetos de diferentes tipos de runtime nunca sejam considerados iguais.

Implementação de classe base

// Shape is an unsealed base class. Making Equals virtual and guarding with GetType()
// ensures a derived instance is never equal to an instance of a different runtime type.
class Shape : IEquatable<Shape>
{
    public string Color { get; }
    public Shape(string color) => Color = color;

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

    // virtual so derived classes can override and augment the comparison
    public virtual bool Equals(Shape? other) =>
        other is not null &&
        GetType() == other.GetType() &&   // reject different runtime types
        Color == other.Color;

    // GetType() is included because equality requires matching runtime types
    public override int GetHashCode() => HashCode.Combine(GetType(), Color);

    public static bool operator ==(Shape? l, Shape? r) => l?.Equals(r) ?? r is null;
    public static bool operator !=(Shape? l, Shape? r) => !(l == r);
}

Pontos principais:

  • virtual digitado Equals: cada classe derivada substitui esse método para aumentar a comparação com seus próprios campos.
  • GetType() proteção: GetType() == other.GetType() impede que um Circle iguale um Shape com a mesma cor e impede que objetos de diferentes tipos derivados se igualem uns aos outros.
  • GetHashCode inclui GetType(): como dois objetos são iguais somente quando seus tipos de runtime correspondem, GetHashCode devem hash do tipo de runtime, bem como os campos de dados. Omitir GetType() aqui causa um comportamento incorreto dentro Dictionary<TKey,TValue> e HashSet<T>.
  • == delegados para Equals: mantém a igualdade de operador e método consistente.

Implementação de classe derivada

Uma classe derivada que adiciona campos substitui o tipo Equals, converte em seu próprio tipo, chama base.Equalse compara seus próprios campos:

class Circle : Shape
{
    public double Radius { get; }
    public Circle(string color, double radius) : base(color) => Radius = radius;

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

    // Calls base.Equals to verify Color and runtime type, then adds Radius
    public override bool Equals(Shape? other) =>
        other is Circle c && base.Equals(c) && Radius == c.Radius;

    public override int GetHashCode() => HashCode.Combine(GetType(), Color, Radius);
}

base.Equals(c) impõe o GetType() guarda e verifica os campos compartilhados. A conversão via other is Circle c falha rapidamente quando o argumento é um Shape de qualquer outro tipo derivado.

Uso por meio de uma variável de tipo base

Shape circle1 = new Circle("red", 5.0);
Shape circle2 = new Circle("red", 7.0);
Shape circle3 = new Circle("red", 5.0);
Shape shape1  = new Shape("red");

Console.WriteLine(circle1.Equals(circle2)); // => False  (Radius differs)
Console.WriteLine(circle1.Equals(circle3)); // => True
Console.WriteLine(circle1.Equals(shape1));  // => False  (different runtime types)

Classes lacradas são mais simples

Você não pode subclasse de uma sealed classe, portanto, os tipos de tempo de compilação e runtime sempre concordam. Você não precisa do GetType() guarda ou virtual expedição. O IEquatable<T> padrão mostrado em Implementar igualdade por conta própria quando um tipo não pode ser um registro está correto e concluído para uma classe lacrada.

Capacidade de sobrecarga do operador

Você pode sobrecarregar os operadores e == os != operadores em um tipo definido pelo usuário. Se você sobrecarregar um desses dois operadores, também deverá sobrecarregar o outro operador.

Você não pode sobrecarregar explicitamente os operadores e == os != operadores em um tipo de registro. Para alterar o comportamento dos operadores e == do != tipo Tde registro, implemente o IEquatable<T>.Equals método com a seguinte assinatura:

public virtual bool Equals(T? other);

Especificação da linguagem C#

Para obter mais informações, consulte a seção Operadores de teste de tipo e relacional na Especificação da linguagem C#.

Para obter mais informações sobre a igualdade de tipos de registro, consulte a seção Membros de igualdade da especificação da linguagem C#.

Confira também