Resolução de Sobrecarga de Método

O enigma desta edição é-nos trazido por Jon Skeet.

Dado o seguinte código:

class X
{
  static int M(Func<int?, byte> x, object y) { return 1; }
  static int M(Func<X, byte> x, string y) { return 2; }

  const int Value = 1000;

  static void Main()
  {
    var a = M(X => (byte)X.Value, null);

    unchecked
    {
      Console.WriteLine(a);
      Console.WriteLine(M(X => (byte)X.Value, null));
    }
  }
}

Qual é o resultado da sua execução?

Resultado

O resultado da execução é:

1
2

O que se passa aqui? Porque é que simplesmente passar a expressão para um bloco unchecked causa um comportamento diferente?

Explicação

A expressão em que nos devemos concentrar é esta:

M(X => (byte)X.Value, null)

Trata-se apenas de uma chamada a um método usando uma expressão lambda, usando resolução de sobrecarga de método (method overload resolution) para determinar qual a sobrecarga a chamar e o tipo de inferência para determinar o tipo de argumentos para o método.

Simplificando a descrição da resolução de sobrecarga de método, seguem-se os seguintes passos:

  • Determina-se que sobrecargas (overloads) são aplicáveis (isto é, quais fazem sentido em termos dos argumentos fornecidos e os correspondentes parâmetros)
  • Compara-se as sobrecargas aplicáveis entre si em termos de qual “a melhor”
  • Se uma sobrecarga é “melhor” que todas as outras, usar essa
  • Se não há sobrecargas aplicáveis, ou nenhuma é melhor que as outras, então a chamada é inválida e leva a um erro de compilação

Pode haver a tentação de saltar diretamente para a segunda opção, assumindo que ambas as sobrecargas são válidas em ambos os casos.

Publicado na edição 44 (PDF) da Revista PROGRAMAR.