Não pode operador == ser aplicado a tipos genéricos em c #?

De acordo com a documentação do operador == no MSDN ,

Para tipos de valores predefinidos, o operador de igualdade (==) retorna true se os valores de seus operandos forem iguais, caso contrário, false. Para tipos de referência diferentes de string, == retorna true se seus dois operandos se referirem ao mesmo object. Para o tipo de string, == compara os valores das strings. Tipos de valor definidos pelo usuário podem sobrecarregar o operador == (consulte o operador). Assim, os tipos de referência definidos pelo usuário, embora por padrão ==, se comportam conforme descrito acima para os tipos de referência predefinidos e definidos pelo usuário.

Então, por que esse trecho de código não compila?

 bool Compare(T x, T y) { return x == y; } 

Eu recebo o erro Operador ‘==’ não pode ser aplicado a operandos do tipo ‘T’ e ‘T’ . Eu me pergunto por que, desde que entendi o operador == é predefinido para todos os tipos?

Edit: Obrigado a todos. Eu não notei a princípio que a declaração era apenas sobre tipos de referência. Também achei que a comparação bit a bit é fornecida para todos os tipos de valor, que agora sei que não estão corretos.

Mas, caso eu esteja usando um tipo de referência, o operador == usaria a comparação de referência predefinida ou usaria a versão sobrecarregada do operador se um tipo fosse definido?

Edição 2: Por tentativa e erro, aprendemos que o operador == usará a comparação de referência predefinida ao usar um tipo genérico irrestrito. Na verdade, o compilador usará o melhor método que puder encontrar para o argumento de tipo restrito, mas não procurará mais. Por exemplo, o código abaixo sempre imprimirá true , mesmo quando Test.test(new B(), new B()) for chamado:

 class A { public static bool operator==(A x, A y) { return true; } } class B : A { public static bool operator==(B x, B y) { return false; } } class Test { void test(T a, T b) where T : A { Console.WriteLine(a == b); } } 

   

“… por padrão == se comporta como descrito acima para os tipos de referência predefinidos e definidos pelo usuário.”

Tipo T não é necessariamente um tipo de referência, portanto, o compilador não pode fazer essa suposição.

No entanto, isso irá compilar porque é mais explícito:

  bool Compare(T x, T y) where T : class { return x == y; } 

Continuação da pergunta adicional, “Mas, caso eu esteja usando um tipo de referência, o operador == usaria a comparação de referência predefinida ou usaria a versão sobrecarregada do operador se um tipo fosse definido?”

Eu teria pensado que == no Generics usaria a versão sobrecarregada, mas o seguinte teste demonstra o contrário. Interessante … adoraria saber o porquê! Se alguém souber por favor compartilhe.

 namespace TestProject { class Program { static void Main(string[] args) { Test a = new Test(); Test b = new Test(); Console.WriteLine("Inline:"); bool x = a == b; Console.WriteLine("Generic:"); Compare(a, b); } static bool Compare(T x, T y) where T : class { return x == y; } } class Test { public static bool operator ==(Test a, Test b) { Console.WriteLine("Overloaded == called"); return a.Equals(b); } public static bool operator !=(Test a, Test b) { Console.WriteLine("Overloaded != called"); return a.Equals(b); } } } 

Saída

Inline: Sobrecarregado == chamado

Genérico:

Pressione qualquer tecla para continuar . . .

Acompanhamento 2

Eu quero ressaltar que mudar meu método de comparação para

  static bool Compare(T x, T y) where T : Test { return x == y; } 

faz com que o operador sobrecarregado == seja chamado. Eu acho que sem especificar o tipo (como um where ), o compilador não pode inferir que deve usar o operador sobrecarregado … embora eu ache que ele teria informações suficientes para tomar essa decisão, mesmo sem especificar o tipo.

Como outros já disseram, só funcionará quando T for restrito a ser um tipo de referência. Sem nenhuma restrição, você pode comparar com null, mas somente null – e essa comparação sempre será falsa para tipos de valor não anuláveis.

Em vez de chamar Equals, é melhor usar um IComparer – e se você não tiver mais informações, o EqualityComparer.Default é uma boa escolha:

 public bool Compare(T x, T y) { return EqualityComparer.Default.Equals(x, y); } 

Além de qualquer outra coisa, isso evita boxe / arremesso.

Em geral, EqualityComparer.Default.Equals deve executar o trabalho com qualquer coisa que implemente IEquatable ou que tenha uma implementação de Equals sensata.

Se, no entanto, == e Equals forem implementados de forma diferente por algum motivo, então meu trabalho em operadores genéricos deve ser útil; suporta as versões de operador (entre outras):

  • Igual (valor T1, valor T2)
  • NotEqual (valor T1, valor T2)
  • GreaterThan (valor T1, valor T2)
  • LessThan (T valor1, valor T2)
  • GreaterThanOrEqual (valor T1, valor T2)
  • LessThanOrEqual (valor T1, valor T2)

Tantas respostas, e nem uma única, explica o porquê? (que Giovanni pediu explicitamente) …

Os genéricos .NET não agem como modelos C ++. Nos modelos C ++, a resolução de sobrecarga ocorre depois que os parâmetros reais do modelo são conhecidos.

Em genéricos .NET (incluindo C #), a resolução de sobrecarga ocorre sem conhecer os parâmetros genéricos reais. As únicas informações que o compilador pode usar para escolher a function a ser chamada são as restrições de tipo nos parâmetros genéricos.

A compilation não pode saber que T não pode ser uma struct (tipo de valor). Então você tem que dizer que só pode ser do tipo de referência eu acho:

 bool Compare(T x, T y) where T : class { return x == y; } 

É porque se T pudesse ser um tipo de valor, poderia haver casos em que x == y seria mal formado – em casos em que um tipo não tem um operador == definido. O mesmo acontecerá com isso, o que é mais óbvio:

 void CallFoo(T x) { x.foo(); } 

Isso também falha, porque você poderia passar um tipo T que não teria uma function foo. C # obriga você a garantir que todos os tipos possíveis sempre tenham uma function foo. Isso é feito pela cláusula where.

Parece que sem a restrição de class:

 bool Compare (T x, T y) where T: class { return x == y; } 

Deve-se perceber que, apesar de class Equals restrita no operador == herdar de Object.Equals , enquanto que de uma estrutura substitui ValueType.Equals .

Observe que:

 bool Compare (T x, T y) where T: struct { return x == y; } 

também fornece o mesmo erro do compilador.

Ainda não entendo porque ter uma comparação de operador de igualdade de tipo de valor é rejeitada pelo compilador. Eu sei de fato, que isso funciona:

 bool Compare (T x, T y) { return x.Equals(y); } 

Há uma input do MSDN Connect para isso aqui

A resposta de Alex Turner começa com:

Infelizmente, esse comportamento é por design e não há uma solução fácil para permitir o uso de == com parâmetros de tipo que podem conter tipos de valor.

Se você quiser ter certeza de que os operadores do seu tipo personalizado são chamados, você pode fazê-lo por meio de reflection. Basta pegar o tipo usando seu parâmetro genérico e recuperar o MethodInfo para o operador desejado (por exemplo, op_Equality, op_Inequality, op_LessThan …).

 var methodInfo = typeof (T).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public); 

Em seguida, execute o operador usando o método Invoke do MethodInfo e passe os objects como os parâmetros.

 var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2}); 

Isso chamará seu operador sobrecarregado e não aquele definido pelas restrições aplicadas no parâmetro genérico. Pode não ser prático, mas pode ser útil para a unidade testando seus operadores ao usar uma class básica genérica que contém alguns testes.

Bem, no meu caso, eu queria testar o operador de igualdade. Eu precisava chamar o código sob os operadores de igualdade sem definir explicitamente o tipo genérico. Os avisos para o EqualityComparer não foram úteis como EqualityComparer chamado método Equals , mas não o operador de igualdade.

Aqui está como eu tenho este trabalho com tipos genéricos, construindo um LINQ . Ele chama o código correto para os operadores == e != :

 ///  /// Gets the result of "a == b" ///  public bool GetEqualityOperatorResult(T a, T b) { // declare the parameters var paramA = Expression.Parameter(typeof(T), nameof(a)); var paramB = Expression.Parameter(typeof(T), nameof(b)); // get equality expression for the parameters var body = Expression.Equal(paramA, paramB); // compile it var invokeEqualityOperator = Expression.Lambda>(body, paramA, paramB).Compile(); // call it return invokeEqualityOperator(a, b); } ///  /// Gets the result of "a =! b" ///  public bool GetInequalityOperatorResult(T a, T b) { // declare the parameters var paramA = Expression.Parameter(typeof(T), nameof(a)); var paramB = Expression.Parameter(typeof(T), nameof(b)); // get equality expression for the parameters var body = Expression.NotEqual(paramA, paramB); // compile it var invokeInequalityOperator = Expression.Lambda>(body, paramA, paramB).Compile(); // call it return invokeInequalityOperator(a, b); } 
bool Compare(T x, T y) where T : class { return x == y; }
bool Compare(T x, T y) where T : class { return x == y; } 

O acima funcionará porque == é cuidado no caso de tipos de referência definidos pelo usuário.
No caso de tipos de valor, == pode ser substituído. Nesse caso, “! =” Também deve ser definido.

Eu acho que poderia ser o motivo, não permite comparação genérica usando “==”.

Eu escrevi a seguinte function olhando para o mais recente msdn. Pode facilmente comparar dois objects x e y :

 static bool IsLessThan(T x, T y) { return ((IComparable)(x)).CompareTo(y) < = 0; }