Eu tenho um cenário em que desejo usar a sintaxe de grupo de método em vez de métodos anônimos (ou sintaxe lambda) para chamar uma função.
A função tem duas sobrecargas, uma que leva um Action
e a outra leva a Func<string>
.
Posso chamar alegremente as duas sobrecargas usando métodos anônimos (ou sintaxe lambda), mas recebo um erro do compilador de invocação ambígua se usar a sintaxe de grupo de métodos. Posso contornar o cast explícito para Action
ou Func<string>
, mas não acho que isso seja necessário.
Alguém pode explicar por que os casts explícitos devem ser exigidos.
Exemplo de código abaixo.
class Program
{
static void Main(string[] args)
{
ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();
// These both compile (lambda syntax)
classWithDelegateMethods.Method(() => classWithSimpleMethods.GetString());
classWithDelegateMethods.Method(() => classWithSimpleMethods.DoNothing());
// These also compile (method group with explicit cast)
classWithDelegateMethods.Method((Func<string>)classWithSimpleMethods.GetString);
classWithDelegateMethods.Method((Action)classWithSimpleMethods.DoNothing);
// These both error with "Ambiguous invocation" (method group)
classWithDelegateMethods.Method(classWithSimpleMethods.GetString);
classWithDelegateMethods.Method(classWithSimpleMethods.DoNothing);
}
}
class ClassWithDelegateMethods
{
public void Method(Func<string> func) { /* do something */ }
public void Method(Action action) { /* do something */ }
}
class ClassWithSimpleMethods
{
public string GetString() { return ""; }
public void DoNothing() { }
}
Atualização C # 7.3
De acordo com o comentário de 0xcde abaixo em 20 de março de 2019 (nove anos após eu postar esta pergunta!), Este código compila a partir do C # 7.3 graças aos candidatos a sobrecarga aprimorados .
<LangVersion>7.3</LangVersion>
) ou posterior, graças aos candidatos a sobrecarga aprimorados .Respostas:
Em primeiro lugar, deixe-me apenas dizer que a resposta de Jon está correta. Esta é uma das partes mais cabeludas da especificação, tão boa para Jon mergulhar nela de cabeça.
Em segundo lugar, deixe-me dizer que esta linha:
(ênfase adicionada) é profundamente enganosa e infeliz. Vou ter uma conversa com Mads sobre como remover a palavra "compatível" aqui.
A razão pela qual isso é enganoso e lamentável é porque parece que isso está chamando a atenção para a seção 15.2, "Compatibilidade de delegados". A Seção 15.2 descreveu a relação de compatibilidade entre métodos e tipos de delegado , mas esta é uma questão de conversibilidade de grupos de métodos e tipos de delegado , que é diferente.
Agora que já resolvemos isso, podemos percorrer a seção 6.6 da especificação e ver o que temos.
Para fazer a resolução de sobrecarga, precisamos primeiro determinar quais sobrecargas são candidatas aplicáveis . Um candidato é aplicável se todos os argumentos forem implicitamente conversíveis para os tipos de parâmetros formais. Considere esta versão simplificada do seu programa:
Então, vamos analisar linha por linha.
Já discuti como a palavra "compatível" é lamentável aqui. Se movendo. Estamos nos perguntando, ao fazer a resolução de sobrecarga em Y (X), o grupo de métodos X é convertido para D1? Ele converte para D2?
Por enquanto, tudo bem. X pode conter um método aplicável às listas de argumentos de D1 ou D2.
Esta linha realmente não diz nada de interessante.
Essa linha é fascinante. Isso significa que existem conversões implícitas que existem, mas que estão sujeitas a serem transformadas em erros! Esta é uma regra bizarra do C #. Para divagar um momento, aqui está um exemplo:
Uma operação de incremento é ilegal em uma árvore de expressão. No entanto, o lambda ainda é conversível para o tipo de árvore de expressão, mesmo que se a conversão for usada, é um erro! O princípio aqui é que podemos querer mudar as regras do que pode ir em uma árvore de expressão posteriormente; alterar essas regras não deve alterar as regras do sistema de tipo . Queremos forçá-lo a tornar seus programas inequívocos agora , de modo que, quando alterarmos as regras para árvores de expressão no futuro para torná-las melhores, não introduzamos alterações significativas na resolução de sobrecarga .
De qualquer forma, este é outro exemplo desse tipo de regra bizarra. Uma conversão pode existir para fins de resolução de sobrecarga, mas pode ser um erro para realmente usar. Embora, na verdade, essa não seja exatamente a situação em que estamos aqui.
Se movendo:
ESTÁ BEM. Portanto, sobrecarregamos a resolução em X em relação a D1. A lista de parâmetros formal de D1 está vazia, então fazemos resolução de sobrecarga em X () e joy, encontramos um método "string X ()" que funciona. Da mesma forma, a lista formal de parâmetros de D2 está vazia. Novamente, descobrimos que "string X ()" é um método que funciona aqui também.
O princípio aqui é que determinar a conversibilidade do grupo de métodos requer a seleção de um método de um grupo de métodos usando a resolução de sobrecarga , e a resolução de sobrecarga não considera os tipos de retorno .
Existe apenas um método no grupo de métodos X, portanto, deve ser o melhor. Provamos com sucesso que existe uma conversão de X para D1 e de X para D2.
Agora, esta linha é relevante?
Na verdade, não, não neste programa. Nunca chegamos ao ponto de ativar essa linha. Porque, lembre-se, o que estamos fazendo aqui é tentar resolver a sobrecarga em Y (X). Temos dois candidatos Y (D1) e Y (D2). Ambos são aplicáveis. Qual é melhor ? Em nenhum lugar da especificação descrevemos a melhor relação entre essas duas conversões possíveis .
Agora, alguém certamente poderia argumentar que uma conversão válida é melhor do que aquela que produz um erro. Isso significaria efetivamente, neste caso, que a resolução de sobrecarga considera os tipos de retorno, que é algo que queremos evitar. A questão então é qual princípio é melhor: (1) manter o invariante de que a resolução de sobrecarga não considera os tipos de retorno, ou (2) tentar escolher uma conversão que sabemos que funcionará sobre outra que sabemos que não funcionará?
Este é um julgamento. Com lambdas , nós fazer considerar o tipo de retorno nesses tipos de conversões, na seção 7.4.3.3:
É lamentável que as conversões de grupo de métodos e conversões lambda sejam inconsistentes a esse respeito. No entanto, posso viver com isso.
De qualquer forma, não temos uma regra de "melhoras" para determinar qual conversão é melhor, X para D1 ou X para D2. Portanto, fornecemos um erro de ambigüidade na resolução de Y (X).
fonte
EDIT: Acho que entendi.
Como diz zinglon, é porque há uma conversão implícita de
GetString
paraAction
, embora o aplicativo de tempo de compilação falhe. Aqui está a introdução à seção 6.6, com alguma ênfase (minha):Agora, eu estava ficando confuso com a primeira frase - que fala sobre uma conversão para um tipo de delegado compatível.
Action
não é um delegado compatível para qualquer método noGetString
grupo de métodos, mas oGetString()
método é aplicável em sua forma normal a uma lista de argumentos construída pelo uso dos tipos de parâmetro e modificadores de D. Observe que isso não fala sobre o tipo de retorno de D. É por isso que está ficando confuso ... porque ele apenas verificaria a compatibilidade do delegadoGetString()
ao aplicar a conversão, não verificando sua existência.Acho que é instrutivo deixar a sobrecarga fora da equação brevemente e ver como essa diferença entre a existência de uma conversão e sua aplicabilidade pode se manifestar. Aqui está um exemplo curto, mas completo:
Nenhuma das expressões de chamada de método em
Main
compilações, mas as mensagens de erro são diferentes. Aqui está aquele paraIntMethod(GetString)
:Em outras palavras, a seção 7.4.3.1 da especificação não pode encontrar nenhum membro de função aplicável.
Ora aqui está o erro para
ActionMethod(GetString)
:Desta vez, ele descobriu o método que deseja chamar - mas não conseguiu realizar a conversão necessária. Infelizmente, não consigo descobrir a parte da especificação em que a verificação final é realizada - parece que pode ser no 7.5.5.1, mas não consigo ver exatamente onde.
A resposta antiga foi removida, exceto por esta parte - porque espero que Eric possa esclarecer o "porquê" dessa pergunta ...
Ainda procurando ... entretanto, se dissermos "Eric Lippert" três vezes, você acha que receberemos uma visita (e, portanto, uma resposta)?
fonte
classWithSimpleMethods.GetString
eclassWithSimpleMethods.DoNothing
não são delegados?ClassWithSimpleMethods.GetString
paraAction
é válida? Para um métodoM
ser compatível com um tipo de delegadoD
(§15.2) "uma identidade ou conversão de referência implícita existe do tipo de retorno deM
para o tipo de retorno deD
."Usar
Func<string>
eAction<string>
(obviamente muito diferente deAction
eFunc<string>
) noClassWithDelegateMethods
remove a ambigüidade.A ambigüidade também ocorre entre
Action
eFunc<int>
.Também recebo o erro de ambiguidade com este:
Outras experiências mostram que, ao passar em um grupo de métodos por conta própria, o tipo de retorno é completamente ignorado ao determinar qual sobrecarga usar.
fonte
A sobrecarga com
Func
eAction
é semelhante (porque ambos são delegados) paraSe você notar, o compilador não sabe qual chamar porque eles diferem apenas pelos tipos de retorno.
fonte
Func<string>
emAction
... e não pode converter um grupo de métodos consistindo apenas em um método que retorna uma string em umAction
.string
para umAction
. Não vejo por que existe ambigüidade.