O seguinte está certo:
try
{
Console.WriteLine("Before");
yield return 1;
Console.WriteLine("After");
}
finally
{
Console.WriteLine("Done");
}
O finally
bloco é executado quando a execução de toda a coisa termina ( IEnumerator<T>
oferece IDisposable
uma maneira de garantir isso mesmo quando a enumeração é abandonada antes de terminar).
Mas isso não está bem:
try
{
Console.WriteLine("Before");
yield return 1; // error CS1626: Cannot yield a value in the body of a try block with a catch clause
Console.WriteLine("After");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Suponha (para fins de argumentação) que uma exceção seja lançada por uma ou outra das WriteLine
chamadas dentro do bloco try. Qual é o problema em continuar a execução em catch
bloco?
Obviamente, a parte do retorno do rendimento é (atualmente) incapaz de lançar qualquer coisa, mas por que isso deveria nos impedir de ter um fechamento try
/ catch
para lidar com exceções lançadas antes ou depois de um yield return
?
Atualização: Há um comentário interessante de Eric Lippert aqui - parece que eles já têm problemas suficientes para implementar o comportamento try / finally corretamente!
EDIT: A página MSDN sobre este erro é: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx . Mas não explica o porquê.
fonte
Respostas:
Suspeito que seja mais uma questão de praticidade do que de viabilidade. Suspeito que haja muito, muito poucas vezes em que essa restrição é realmente um problema que não pode ser contornado - mas a complexidade adicional no compilador seria muito significativa.
Existem algumas coisas assim que eu já encontrei:
Em cada um desses casos, seria possível ganhar um pouco mais de liberdade, à custa de complexidade extra no compilador. A equipe fez a escolha pragmática, pela qual os aplaudo - prefiro uma linguagem um pouco mais restritiva com um compilador 99,9% preciso (sim, há bugs; encontrei um no SO outro dia) do que mais linguagem flexível que não pode ser compilada corretamente.
EDIT: Aqui está uma pseudo-prova de como isso é viável.
Considere isso:
Agora transforme:
em (espécie de pseudocódigo):
A única duplicação está na configuração de blocos try / catch - mas isso é algo que o compilador certamente pode fazer.
Posso muito bem ter perdido algo aqui - se sim, por favor, me avise!
fonte
using
eforeach
. Por exemplo:try{foreach (string s in c){yield return s;}}catch(Exception){}
yield
, na minha opinião, por causa do código espaguete que você tem que escrever para contorná-lo.yield
IMO - está longe de ser severo .Todas as
yield
instruções em uma definição de iterador são convertidas em um estado em uma máquina de estado que usa efetivamente umaswitch
instrução para avançar estados. Se ele fez gerar o código parayield
instruções em um try / catch que teria de duplicar tudo notry
bloco para cadayield
declaração, excluindo todas as outrasyield
declaração para esse bloco. Isso nem sempre é possível, principalmente se umayield
instrução depender de outra anterior.fonte
Eu especularia que, devido à maneira como a pilha de chamadas é enrolada / desenrolada quando você produz o retorno de um enumerador, torna-se impossível para um bloco try / catch realmente "capturar" a exceção. (porque o bloco de retorno de rendimento não está na pilha, embora ele tenha originado o bloco de iteração)
Para obter uma ideia do que estou falando, configure um bloco iterador e um foreach usando esse iterador. Verifique a aparência da pilha de chamadas dentro do bloco foreach e, em seguida, verifique-a dentro do bloco iterador try / finally.
fonte
Aceitei a resposta de THE INVINCIBLE SKEET até que alguém da Microsoft apareceu para colocar água fria na ideia. Mas não concordo com a parte da questão de opinião - é claro que um compilador correto é mais importante do que um completo, mas o compilador C # já é muito inteligente em resolver essa transformação para nós na medida do possível. Um pouco mais de completude neste caso tornaria a linguagem mais fácil de usar, ensinar, explicar, com menos casos extremos ou pegadinhas. Então, acho que valeria a pena o esforço extra. Alguns caras em Redmond coçam a cabeça por quinze dias e, como resultado, milhões de programadores na próxima década podem relaxar um pouco mais.
(Eu também nutro um desejo sórdido de que haja uma maneira de
yield return
lançar uma exceção que foi inserida na máquina de estado "de fora", pelo código que conduz a iteração. Mas minhas razões para querer isso são bastante obscuras.)Na verdade, uma dúvida que tenho sobre a resposta de Jon tem a ver com o lançamento da expressão de retorno de rendimento.
Obviamente, o retorno de rendimento 10 não é tão ruim. Mas isso seria ruim:
Portanto, não faria mais sentido avaliar isso dentro do bloco try / catch anterior:
O próximo problema seria blocos try / catch aninhados e exceções relançadas:
Mas tenho certeza que é possível ...
fonte