O compilador do C# tem um algoritmo de resolução de overload de métodos onde, sempre que uma invocação de método aparece, ela é verificada para determinar com qual método a invocação será ligada. Essa verificação é feita em tempo de compilação, o que, com Visual Studio, significa sempre que você para de digitar por alguns segundos, ou quando pede uma compilação explicitamente. Esse algorítimo é conhecído internamente no time de linguagens como algorítimo de “Betterness”, ou em tradução livre, algorítmo “do quanto melhor”.

Esse algorítmo é quem determina, por exemplo, que quando você tem dois métodos com a mesma assinatura, mas um é um método de extensão e o outro é de instância, que o de instância sempre é escolhido como o melhor.

Todo mundo sabe que o compilador do C# foi reescrito em C#, no chamado projeto Roslyn. Isso permitiu evoluir esse algoritmo um pouco. Isso é o que eles tem chamado de “Better Betterness”, e é umas novidades no C# 6. No artigo sobre novidades do C# 6 que está no wiki do repositório oficial do Github há um tópico sobre “Improved Overload Resolution”, mas eles não detalham muito, porque basicamente significa explicar algo que “simplesmente funciona”. Ou deveria funcionar.

Digo deveria funcionar, porque algumas coisas no C# 5 não funcionam como gostaríamos. Apesar de serem poucas, elas são chatinhas. Por exemplo, esse código não compila no Visual Studio 2013, que só vai até o C# 5:

public class Class1
{
    static System.Threading.Tasks.Task FunctionAsync() { return null; }
    static void Foo()
    {
        System.Threading.Tasks.Task.Run(FunctionAsync);
    }
}

Tente compilar ele no Visual Studio 2013, e verá (clique para ampliar):

Visual Studio 2013

Já no Visual Studio 2015:

Visual Studio 2015

O C# 5 não consegue compilar e enxerga uma ambiguidade na chamada porque o algorítimo de betterness não utiliza, na hora de escolher o overload de um método, o tipo do retorno dos métodos de um “method group”. “Method group” é o nome dado na especificação do C# à referência a um grupo de membros de um tipo (classe, estrutura, etc) que são métodos e compartilham o mesmo identificador. Por exemplo, no contexto do código acima, na referência que está no corpo do método “Foo”, “FunctionAsync” é um “method group”. A invocação se consegue colocando parêntenses após esse identificador: “FunctionAsync()”, e nesse caso o “method group” está sendo parte de uma expressão de chamada.

O método “Run” da class “Task” possui diversos overloads, e entre eles há um que recebe uma “Func<Task>” e outro que recebe uma “Action”. O C# permite que passemos “method groups” como parâmetros caso o argumento receba uma lambda de assinatura compatível. Como o método “FuncionAsync” pode funcionar das duas formas, podendo ser visto como “Action” ou “Func<Task>”, e o compilador do C# 5 ignorava o retorno, que era a única coisa que diverenciava os dois, ele lançava um erro indicando essa ambiguidade. O compilador do C# 6 utiliza esse tipo de retorno dos diversos métodos de um “method group” para diferenciar qual sobrecarga será ligada à invocação, portanto ele consegue compilar esse código.

É possível resolver esse problema no C# 5, basta utilizar uma lambda. Fica assim:

public class Class1
{
    static System.Threading.Tasks.Task FunctionAsync() { return null; }
    static void Foo()
    {
        System.Threading.Tasks.Task.Run(() => FunctionAsync());
    }
}

O problema é que esse código acaba gerando um problema de desempenho, já que existe uma alocação de memória desnecessária para a criação da lambda.  Outra forma é assim:

static System.Threading.Tasks.Task FunctionAsync() { return null; }
static void Foo()
{
    System.Threading.Tasks.Task.Run((Func<Task>)FunctionAsync);
}

Nesse caso não existe a lambda extra, mas também não fica bonito. No C# 6 o cast é desnecessário, e o primeiro código que está no começo do artigo compila normalmente, escolhendo o overload esperado, que espera uma “Func<Task>”.

Se você se interessou pelo algorítimo de betterness e quer entender ele no detalhe vale a pena ler seus detalhes na especificação do C#. Ela vem instalada junto com o Visual Studio (normalmente fica em “C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC#\Specifications\1033”) e também pode ser baixada online. Procure a seção 7.5.3, com título “Overload Resolution” e divirta-se.

Uma curiosidade interessante: O Visual Basic 12, versão que vem no Visual Studio 2013, já utiliza o retorno de “method groups” na hora de escolher a melhor sobrecarga. 🙂

Outra curiosidade: o algorítmo de betterness deve continuar evoluindo. Essa proposta no Github pretende levá-lo ainda mais longe, e o autor escolheu o nome de “bestest betterness”. Nem quero imaginar o nome que vem depois desse.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.