Por que um método anônimo não pode ser atribuído a var?

139

Eu tenho o seguinte código:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

No entanto, o seguinte não é compilado:

var comparer = delegate(string value) {
    return value != "0";
};

Por que o compilador não pode descobrir que é um Func<string, bool>? Ele pega um parâmetro de string e retorna um booleano. Em vez disso, ele me dá o erro:

Não é possível atribuir método anônimo a uma variável local de tipo implícito.

Eu tenho um palpite e que, se a versão var compilada , falta consistência se eu tivesse o seguinte:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

O exemplo acima não faria sentido, pois o Func <> permite apenas até 4 argumentos (no .NET 3.5, que é o que estou usando). Talvez alguém possa esclarecer o problema. Obrigado.

Marlon
fonte
3
Observe sobre seu argumento de 4 argumentos , no .NET 4, Func<>aceita até 16 argumentos.
Anthony Pegram
Obrigado pelo esclarecimento. Estou usando o .NET 3.5.
Marlon
9
Por que faria o compilador pensar que é um Func<string, bool>? Parece um Converter<string, bool>para mim!
Ben Voigt
3
às vezes eu sinto falta VB ..Dim comparer = Function(value$) value <> "0"
Slai

Respostas:

155

Outros já apontaram que existem infinitos tipos possíveis de delegados que você poderia ter significado; o que é de cerca de tão especial Funcque merece ser o padrão em vez de Predicateou Actionou qualquer outra possibilidade? E, para lambdas, por que é óbvio que a intenção é escolher a forma delegada, em vez da forma da árvore de expressão?

Mas poderíamos dizer que Funcé especial e que o tipo inferido de um método lambda ou anônimo é Func de alguma coisa. Ainda teríamos todos os tipos de problemas. Que tipos você gostaria de deduzir para os seguintes casos?

var x1 = (ref int y)=>123;

Não existe um Func<T>tipo que faça referência a nada.

var x2 = y=>123;

Não sabemos o tipo do parâmetro formal, apesar de conhecermos o retorno. (Ou nós? O retorno é int? Long? Short? Byte?)

var x3 = (int y)=>null;

Não sabemos o tipo de retorno, mas não pode ser anulado. O tipo de retorno pode ser qualquer tipo de referência ou qualquer tipo de valor anulável.

var x4 = (int y)=>{ throw new Exception(); }

Novamente, não sabemos o tipo de retorno e, desta vez, pode ser anulado.

var x5 = (int y)=> q += y;

Isso pretende ser uma instrução lambda de retorno nulo ou algo que retorne o valor que foi atribuído a q? Ambos são legais; qual devemos escolher?

Agora, você pode dizer, bem, apenas não suporte nenhum desses recursos. Apenas suporte casos "normais" em que os tipos possam ser trabalhados. Isso não ajuda. Como isso facilita minha vida? Se o recurso funcionar algumas vezes e falhar algumas vezes, ainda tenho que escrever o código para detectar todas essas situações de falha e fornecer uma mensagem de erro significativa para cada uma. Ainda precisamos especificar todo esse comportamento, documentá-lo, escrever testes e assim por diante. Esse é um recurso muito caro que economiza ao usuário meia dúzia de pressionamentos de tecla. Temos maneiras melhores de agregar valor ao idioma do que gastar muito tempo escrevendo casos de teste para um recurso que não funciona na metade do tempo e que não oferece quase nenhum benefício nos casos em que funciona.

A situação em que é realmente útil é:

var xAnon = (int y)=>new { Y = y };

porque não existe um tipo "falável" para essa coisa. Mas temos esse problema o tempo todo e usamos apenas a inferência de tipo de método para deduzir o tipo:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

e agora a inferência de tipo de método descobre qual é o tipo de função.

Eric Lippert
fonte
43
Quando você compilará suas respostas de SO em um livro? Eu comprá-lo :) #
91111 Matt Greer
13
Segundo a proposta de um livro de Eric Lippert com respostas para SO. Título sugerido: "Reflexões da pilha"
Adam Rackis 17/02/11
24
@Eric: Boa resposta, mas é um pouco enganador ilustrar isso como algo que não é possível, pois isso realmente funciona muito bem em D. É que vocês não escolheram dar aos literais delegados seu próprio tipo e, em vez disso, os fizeram depender em seus contextos ... então IMHO a resposta deve ser "porque foi assim que fizemos" mais do que qualquer outra coisa. :)
user541686
5
@abstractdissonance Note também que o compilador é de código aberto. Se você se preocupa com esse recurso, pode doar o tempo e o esforço necessários para que isso aconteça. Convido você a enviar uma solicitação de recebimento.
precisa
7
@AbstractDissonance: medimos o custo em termos de recursos limitados: desenvolvedores e tempo. Essa responsabilidade não foi concedida por Deus; foi imposto pelo vice-presidente da divisão de desenvolvedores. A noção de que de alguma forma a equipe de C # poderia ignorar um processo de orçamento é estranha. Garanto que as trocas foram e ainda são feitas pela consideração cuidadosa e cuidadosa de especialistas que tiveram as comunidades C # expressadas os desejos, a missão estratégica da Microsoft e seu excelente gosto em design.
Eric Lippert
29

Apenas Eric Lippert sabe ao certo, mas acho que é porque a assinatura do tipo de delegado não determina exclusivamente o tipo.

Considere o seu exemplo:

var comparer = delegate(string value) { return value != "0"; };

Aqui estão duas inferências possíveis para o que vardeveria ser:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

Qual o compilador deve inferir? Não há um bom motivo para escolher um ou outro. E embora a Predicate<T>seja funcionalmente equivalente a Func<T, bool>, eles ainda são tipos diferentes no nível do sistema de tipos .NET. O compilador, portanto, não pode resolver inequivocamente o tipo de delegado e deve falhar na inferência de tipo.

Itowlson
fonte
1
Tenho certeza de que muitas outras pessoas da Microsoft também sabem ao certo. ;) Mas sim, você alude a uma razão principal, o tipo de tempo de compilação não pode ser determinado porque não existe. A seção 8.5.1 da especificação de idioma destaca especificamente esse motivo para impedir que funções anônimas sejam usadas em declarações de variáveis ​​implicitamente digitadas.
Anthony Pegram
3
Sim. E pior ainda, para lambdas, nem sabemos se está indo para um tipo de delegado; pode ser uma árvore de expressão.
Eric Lippert
Para quem estiver interessado, eu escrevi um pouco mais sobre este e como o C # e F # se aproxima de contraste em mindscapehq.com/blog/index.php/2011/02/23/...
itowlson
por que não pode o compilador apenas fabricar um novo tipo único, como C ++ faz para a sua função lambda
Weipeng L
Como eles diferem "no nível do sistema de tipos .NET"?
Arao6
6

Eric Lippert tem um post antigo sobre o assunto, onde ele diz

E, de fato, a especificação C # 2.0 chama isso. Expressões de grupo de método e expressões de método anônimas são expressões sem tipo no C # 2.0, e expressões lambda se juntam a elas no C # 3.0. Portanto, é ilegal que eles apareçam "nus" no lado direito de uma declaração implícita.

Brian Rasmussen
fonte
E isso é sublinhado na seção 8.5.1 da especificação de idioma. "A expressão inicializadora deve ter um tipo de tempo de compilação" para ser usada para uma variável local digitada implicitamente.
Anthony Pegram
5

Delegados diferentes são considerados tipos diferentes. por exemplo, Actioné diferente de MethodInvokere uma instância de Actionnão pode ser atribuída a uma variável do tipo MethodInvoker.

Então, dado um delegado anônimo (ou lambda) como () => {}, é um Actionou um MethodInvoker? O compilador não pode dizer.

Da mesma forma, se eu declarar um tipo de delegado pegando um stringargumento e retornando um bool, como o compilador saberia que você realmente queria um em Func<string, bool>vez do meu tipo de delegado? Não pode inferir o tipo de delegado.

Stephen Cleary
fonte
2

Os seguintes pontos são do MSDN em relação às variáveis ​​locais de tipo implícito:

  1. var só pode ser usado quando uma variável local é declarada e inicializada na mesma instrução; a variável não pode ser inicializada como nula ou para um grupo de métodos ou uma função anônima.
  2. A palavra-chave var instrui o compilador a inferir o tipo da variável da expressão no lado direito da instrução de inicialização.
  3. É importante entender que a palavra-chave var não significa "variante" e não indica que a variável é de tipo fraco ou com ligação tardia. Significa apenas que o compilador determina e atribui o tipo mais apropriado.

Referência do MSDN: variáveis ​​locais implicitamente digitadas

Considerando o seguinte em relação aos métodos anônimos:

  1. Métodos anônimos permitem omitir a lista de parâmetros.

Referência do MSDN: Métodos Anônimos

Eu suspeitaria que, como o método anônimo pode realmente ter assinaturas de métodos diferentes, o compilador não pode inferir adequadamente qual seria o tipo mais apropriado a ser atribuído.

nybbler
fonte
1

Minha postagem não responde à pergunta real, mas responde à pergunta subjacente de:

"Como evito ter que digitar algum tipo fugitivo como Func<string, string, int, CustomInputType, bool, ReturnType>?" [1]

Sendo o programador preguiçoso / hacky que sou, experimentei usar Func<dynamic, object>- o que pega um único parâmetro de entrada e retorna um objeto.

Para vários argumentos, você pode usá-lo da seguinte maneira:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

Dica: você pode usar Action<dynamic>se não precisar retornar um objeto.

Sim, eu sei que provavelmente vai contra seus princípios de programação, mas isso faz sentido para mim e provavelmente para alguns codificadores Python.

Sou muito novato nos delegados ... só queria compartilhar o que aprendi.


[1] Isso pressupõe que você não está chamando um método que requer Funcum parâmetro predefinido. Nesse caso, você precisará digitar a sequência fugly: /

Ambrose Leung
fonte
0

Como é isso?

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

string result = item.toolPath(item.toolisn, item.LangId);
mmm
fonte