Por que o compilador C # não falha no código onde um método estático chama um método de instância?

110

O código a seguir tem um método estático Foo(), chamando um método de instância Bar():

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

Ele compila sem erros * mas gera uma exceção de fichário de tempo de execução no tempo de execução. Remover o parâmetro dinâmico para esses métodos causa um erro do compilador, como esperado.

Então, por que ter um parâmetro dinâmico permite que o código seja compilado? O ReSharper também não mostra isso como um erro.

Edição 1: * no Visual Studio 2008

Edição 2: adicionada sealedporque é possível que uma subclasse contenha um Bar(...)método estático . Mesmo a versão selada compila quando não é possível que qualquer método diferente do método de instância possa ser chamado em tempo de execução.

Mike Scott
fonte
8
1 para pergunta muito boa
cuongle
40
Esta é uma pergunta de Eric-Lippert.
Olivier Jacot-Descombes
3
tenho certeza que Jon Skeet saberia o que fazer com isso também;) @ OlivierJacot-Descombes
Mil,
2
@Olivier, Jon Skeet provavelmente queria que o código compilasse, então o compilador permite :-))
Mike Scott
5
Este é outro exemplo de por que você não deve usar a dynamicmenos que seja realmente necessário.
Serviço de

Respostas:

71

ATUALIZAÇÃO: a resposta abaixo foi escrita em 2012, antes da introdução do C # 7.3 (maio de 2018) . Em O que há de novo no C # 7.3 , a seção Candidatos de sobrecarga aprimorados , item 1, é explicado como as regras de resolução de sobrecarga foram alteradas para que as sobrecargas não estáticas sejam descartadas antecipadamente. Portanto, a resposta abaixo (e toda a questão) tem principalmente interesse apenas histórico agora!


(Pré C # 7.3 :)

Por alguma razão, a resolução de sobrecarga sempre encontra a melhor correspondência antes de verificar se há estática versus não estática. Tente este código com todos os tipos estáticos:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

Isso não irá compilar porque a melhor sobrecarga é aquela que leva a string. Mas ei, esse é um método de instância, então o compilador reclama (em vez de aceitar a segunda melhor sobrecarga).

Adição: Então, acho que a explicação do dynamicexemplo da Questão Original é que, para ser consistente, quando os tipos são dinâmicos, também encontramos primeiro a melhor sobrecarga (verificando apenas o número do parâmetro e os tipos de parâmetro etc., não estático x não -static), e só então verifique se há estática. Mas isso significa que a verificação estática deve esperar até o tempo de execução. Daí o comportamento observado.

Adição tardia: Algumas informações básicas sobre por que eles escolheram fazer as coisas nessa ordem engraçada podem ser inferidas a partir desta postagem do blog de Eric Lippert .

Jeppe Stig Nielsen
fonte
Não há sobrecargas na pergunta original. As respostas que mostram uma sobrecarga estática não são relevantes. Não é válido responder "bem, se você escreveu isso ..." já que eu não escrevi isso :-)
Mike Scott
5
@MikeScott Eu apenas tento convencê-lo de que a resolução de sobrecarga em C # sempre é assim: (1) Encontre a melhor correspondência, desconsiderando estático / não estático. (2) Agora sabemos qual sobrecarga usar, então verifique se há estática. Por isso, quando dynamicfoi introduzido na linguagem, acho que os designers do C # disseram: "Não consideraremos (2) o tempo de compilação quando for uma dynamicexpressão." Portanto, meu objetivo aqui é ter uma ideia de por que eles optaram por não verificar a existência de estática versus instância até o tempo de execução. Eu diria que essa verificação acontece no momento da vinculação .
Jeppe Stig Nielsen
justo o suficiente, mas ainda não explica por que, neste caso, o compilador não pode resolver a chamada para o método de instância. Em outras palavras, a maneira como o compilador faz a resolução é simplista - ele não reconhece o caso simples como meu exemplo, onde não há possibilidade de que ele não consiga resolver a chamada. A ironia é: por ter um único método Bar () com um parâmetro dinâmico, o compilador então ignora esse único método Bar ().
Mike Scott,
45
Escrevi esta parte do compilador C # e Jeppe está certo. Por favor, vote. A resolução de sobrecarga acontece antes de verificar se um determinado método é estático ou um método de instância e, neste caso, adiamos a resolução de sobrecarga para o tempo de execução e, portanto, também a verificação estática / instância até o tempo de execução. Além disso, o compilador faz o "melhor esforço" para encontrar estaticamente erros dinâmicos que não sejam abrangentes.
Chris Burrows,
30

Foo tem um parâmetro "x" que é dinâmico, o que significa que Bar (x) é uma expressão dinâmica.

Seria perfeitamente possível que o Exemplo tivesse métodos como:

static Bar(SomeType obj)

Nesse caso, o método correto seria resolvido, de modo que a declaração Bar (x) é perfeitamente válida. O fato de haver um método de instância Bar (x) é irrelevante e nem mesmo considerado: por definição , como Bar (x) é uma expressão dinâmica, adiamos a resolução para o tempo de execução.

Marc Gravell
fonte
14
mas quando você tira o método Bar de instância, ele não compila mais.
Justin Harvey,
1
@Justin interessante - um aviso? Ou um erro? De qualquer forma, ele pode validar apenas até o grupo de métodos, deixando a resolução de sobrecarga total para o tempo de execução.
Marc Gravell
1
@Marc, já que não há outro método Bar (), você não está respondendo à pergunta. Você pode explicar isso, visto que há apenas um método Bar () sem sobrecargas? Por que adiar para o tempo de execução quando não há como qualquer outro método ser chamado? Ou existe? Nota: Eu editei o código para selar a classe, que ainda compila.
Mike Scott,
1
@mike para saber por que adiar para o tempo de execução: porque é isso que dinâmico significa
Marc Gravell
2
@Mike impossível não é o ponto; o que é importante é se é necessário . O ponto principal com dynamic é que não é tarefa do compilador.
Marc Gravell
9

A expressão "dinâmica" será vinculada durante o tempo de execução, portanto, se você definir um método estático com a assinatura correta ou um método de instância, o compilador não o verificará.

O método "certo" será determinado durante a execução. O compilador não pode saber se existe um método válido durante a execução.

A palavra-chave "dynamic" é definida para linguagens dinâmicas e de script, onde o Método pode ser definido a qualquer momento, mesmo durante a execução. Coisas doidas

Aqui, um exemplo que lida com ints, mas sem strings, por causa do método está na instância.

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

Você pode adicionar um método para tratar todas as chamadas "erradas", que não puderam ser tratadas

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}
oberfreak
fonte
O código de chamada em seu exemplo não deveria ser Example.Bar (...) em vez de Example.Foo (...)? Foo () não é irrelevante em seu exemplo? Eu realmente não entendo seu exemplo. Por que adicionar o método genérico estático causaria um problema? Você poderia editar sua resposta para incluir esse método em vez de fornecê-lo como uma opção?
Mike Scott,
mas o exemplo que postei tem apenas um método de instância única e nenhuma sobrecarga, portanto, em tempo de compilação, você sabe que não há métodos estáticos possíveis que possam ser resolvidos. Somente se você adicionar pelo menos um, a situação muda e o código é válido.
Mike Scott,
Mas este exemplo ainda tem mais de um método Bar (). Meu exemplo tem apenas um método. Portanto, não há possibilidade de chamar qualquer método Bar () estático. A chamada pode ser resolvida em tempo de compilação.
Mike Scott,
@Mike pode ser! = É; com dinâmico, não é necessário fazê-lo
Marc Gravell
@MarcGravell, por favor, esclareça?
Mike Scott,