A palavra-chave yield é uma daquelas palavras-chave em C # que continuam me intrigando, e nunca tive certeza de que estou usando-a corretamente.
Dos dois pedaços de código a seguir, qual é o preferido e por quê?
Versão 1: Usando retorno de rendimento
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
foreach (Product product in products)
{
yield return product;
}
}
}
Versão 2: retornar a lista
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
return products.ToList<Product>();
}
}
c#
yield-return
senfo
fonte
fonte
yield
está ligado aIEnumerable<T>
e seu tipo. É de alguma maneira avaliação preguiçosayield return
se o código que itera nos resultados deGetAllProducts()
permite ao usuário a chance de cancelar prematuramente o processamento.Respostas:
Costumo usar o retorno do rendimento quando calculo o próximo item da lista (ou mesmo o próximo grupo de itens).
Usando sua versão 2, você deve ter a lista completa antes de retornar. Ao usar o retorno do rendimento, você realmente só precisa do próximo item antes de retornar.
Entre outras coisas, isso ajuda a espalhar o custo computacional de cálculos complexos por um período maior. Por exemplo, se a lista estiver conectada a uma GUI e o usuário nunca for para a última página, você nunca calculará os itens finais na lista.
Outro caso em que o retorno do rendimento é preferível é se o IEnumerable representa um conjunto infinito. Considere a lista de números primos ou uma lista infinita de números aleatórios. Você nunca pode retornar o IEnumerable completo de uma só vez, portanto, use yield-return para retornar a lista incrementalmente.
No seu exemplo específico, você tem a lista completa de produtos, portanto, eu usaria a Versão 2.
fonte
Yield return
parece ser uma abreviação para escrever sua própria classe de iterador personalizado (implementar IEnumerator). Portanto, os benefícios mencionados também se aplicam às classes do iterador customizado. De qualquer forma, ambas as construções mantêm o estado intermediário. Na sua forma mais simples, trata-se de manter uma referência ao objeto atual.Preencher uma lista temporária é como baixar o vídeo inteiro, enquanto usar
yield
é como transmitir esse vídeo.fonte
Como um exemplo conceitual para entender quando você deve usar
yield
, digamos que o métodoConsumeLoop()
processe os itens retornados / produzidos porProduceList()
:Sem
yield
, a chamada paraProduceList()
pode demorar muito, pois você precisa concluir a lista antes de retornar:Usando
yield
, torna-se reorganizado, meio que trabalhando "em paralelo":E, finalmente, como muitos já sugeriram, você deve usar a versão 2, porque você já tem a lista completa de qualquer maneira.
fonte
Sei que essa é uma pergunta antiga, mas gostaria de oferecer um exemplo de como a palavra-chave yield pode ser usada de forma criativa. Eu realmente me beneficiei dessa técnica. Espero que isso ajude qualquer pessoa que tropeça nessa questão.
Nota: Não pense na palavra-chave yield como sendo apenas outra maneira de criar uma coleção. Uma grande parte do poder do rendimento vem do fato de a execução ser pausada em seu método ou propriedade até que o código de chamada itere sobre o próximo valor. Aqui está o meu exemplo:
O uso da palavra-chave yield (ao lado da implementação Caliburn.Micro coroutines de Rob Eisenburg ) permite expressar uma chamada assíncrona para um serviço da Web como este:
O que isso fará é ativar meu BusyIndicator, chamar o método Login no meu serviço da web, definir meu sinalizador IsLoggedIn com o valor de retorno e desativar o BusyIndicator.
Eis como isso funciona: IResult possui um método Execute e um evento Completed. Caliburn.Micro pega o IEnumerator da chamada para HandleButtonClick () e o passa para um método Coroutine.BeginExecute. O método BeginExecute começa a iterar pelos IResults. Quando o primeiro IResult é retornado, a execução é pausada dentro de HandleButtonClick () e BeginExecute () anexa um manipulador de eventos ao evento Completed e chama Execute (). IResult.Execute () pode executar uma tarefa síncrona ou assíncrona e dispara o evento Concluído quando terminar.
O LoginResult é mais ou menos assim:
Pode ajudar a configurar algo assim e avançar na execução para observar o que está acontecendo.
Espero que isso ajude alguém! Eu realmente gostei de explorar as diferentes maneiras pelas quais o rendimento pode ser usado.
fonte
yield
dessa maneira. Parece uma maneira elegante de emular o padrão async / waitit (que eu assumo que seria usado em vez deyield
se isso fosse reescrito hoje). Você descobriu que esses usos criativosyield
produziram (sem trocadilhos) retornos decrescentes ao longo dos anos à medida que o C # evoluiu desde que você respondeu a essa pergunta? Ou você ainda está apresentando casos de uso inteligentes e modernizados como esse? E se sim, você se importaria de compartilhar outro cenário interessante para nós?Isso vai parecer uma sugestão bizarra, mas eu aprendi como usar a
yield
palavra - chave em C # lendo uma apresentação sobre geradores em Python: David M. Beazley's http://www.dabeaz.com/generators/Generators.pdf . Você não precisa conhecer muito Python para entender a apresentação - eu não. Achei muito útil explicar não apenas como os geradores funcionam, mas por que você deveria se importar.fonte
O retorno do rendimento pode ser muito poderoso para algoritmos nos quais você precisa iterar através de milhões de objetos. Considere o exemplo a seguir, onde você precisa calcular possíveis viagens para compartilhamento de viagens. Primeiro, geramos possíveis viagens:
Em seguida, percorra cada viagem:
Se você usar Lista em vez de rendimento, precisará alocar 1 milhão de objetos na memória (~ 190mb) e este exemplo simples levará ~ 1400ms para executar. No entanto, se você usar yield, não precisará colocar todos esses objetos temporários na memória e obterá uma velocidade do algoritmo significativamente mais rápida: este exemplo levará apenas ~ 400ms para ser executado sem nenhum consumo de memória.
fonte
yield
trabalha sob a proteção implementando uma máquina de estado internamente. Aqui está uma resposta SO com 3 postagens detalhadas do blog do MSDN que explicam a implementação em detalhes. Escrito por Raymond Chen @ MSFTOs dois pedaços de código estão realmente fazendo duas coisas diferentes. A primeira versão puxará os membros conforme você precisar. A segunda versão carregará todos os resultados na memória antes de você começar a fazer alguma coisa.
Não há resposta certa ou errada para esta. Qual deles é preferível depende apenas da situação. Por exemplo, se houver um limite de tempo para concluir sua consulta e você precisar fazer algo semi-complicado com os resultados, a segunda versão poderá ser preferível. Mas tenha cuidado com grandes conjuntos de resultados, especialmente se você estiver executando esse código no modo de 32 bits. Fui mordido por exceções OutOfMemory várias vezes ao fazer esse método.
O principal a ter em mente é o seguinte: as diferenças estão na eficiência. Portanto, você provavelmente deve escolher o que simplificar seu código e alterá-lo somente após a criação de perfil.
fonte
O rendimento tem dois grandes usos
Ajuda a fornecer iteração personalizada sem criar coleções temporárias. (carregando todos os dados e fazendo loop)
Ajuda a fazer iteração com estado. ( transmissão)
Abaixo está um vídeo simples que eu criei com demonstração completa para apoiar os dois pontos acima
http://www.youtube.com/watch?v=4fju3xcm21M
fonte
É o que Chris Sells conta sobre essas declarações na linguagem de programação C # ;
fonte
F().Any()
- isso retornará depois de tentar enumerar apenas o primeiro resultado. Em geral, você não deve confiar em umIEnumerable yield
para alterar o estado do programa, porque ele não pode realmente se desencadeouSupondo que a classe LINQ de seus produtos use um rendimento semelhante para enumerar / iterar, a primeira versão é mais eficiente, pois apenas gera um valor por cada iteração.
O segundo exemplo é converter o enumerador / iterador em uma lista com o método ToList (). Isso significa que itera manualmente sobre todos os itens no enumerador e, em seguida, retorna uma lista simples.
fonte
Isso está além do ponto, mas como a pergunta está marcada como boas práticas, vou adiante e coloco meus dois centavos. Para esse tipo de coisa, prefiro transformá-lo em uma propriedade:
Claro, é um pouco mais resistente, mas o código que usa isso parecerá muito mais limpo:
vs
Nota: Eu não faria isso por nenhum método que demore um pouco para fazer seu trabalho.
fonte
E isso?
Eu acho que isso é muito mais limpo. Não tenho o VS2008 em mãos para verificar. Em qualquer caso, se Products implementa IEnumerable (como parece - é usado em uma instrução foreach), eu a retornaria diretamente.
fonte
Eu teria usado a versão 2 do código neste caso. Como você tem a lista completa de produtos disponíveis e é isso que o "consumidor" espera dessa chamada de método, seria necessário enviar as informações completas de volta ao chamador.
Se o chamador deste método exigir "uma" informação de cada vez e o consumo da próxima informação for sob demanda, seria benéfico usar o retorno de rendimento que garantirá que o comando de execução seja retornado ao chamador quando uma unidade de informação está disponível.
Alguns exemplos em que se poderia usar retorno de rendimento são:
Para responder suas perguntas, eu teria usado a versão 2.
fonte
Retorne a lista diretamente. Benefícios:
A lista é reutilizável. (o iterador não é)não é verdade, obrigado JonVocê deve usar o iterador (rendimento) a partir de quando achar que provavelmente não precisará repetir todo o caminho até o final da lista ou quando ele não tiver fim. Por exemplo, a chamada do cliente procurará o primeiro produto que satisfaça algum predicado, você pode considerar usar o iterador, embora esse seja um exemplo artificial e provavelmente haja maneiras melhores de realizá-lo. Basicamente, se você souber com antecedência que toda a lista precisará ser calculada, faça-a com antecedência. Se você acha que não, considere usar a versão do iterador.
fonte
A frase-chave de retorno de rendimento é usada para manter a máquina de estado para uma coleção específica. Sempre que o CLR vê a frase-chave de retorno de rendimento sendo usada, o CLR implementa um padrão de enumerador para esse trecho de código. Esse tipo de implementação ajuda o desenvolvedor de todo o tipo de canalização que, de outra forma, teríamos que fazer na ausência da palavra-chave.
Suponha que, se o desenvolvedor estiver filtrando alguma coleção, iterando pela coleção e extraindo esses objetos em alguma nova coleção. Esse tipo de encanamento é bastante monótono.
Mais sobre a palavra-chave aqui neste artigo .
fonte
O uso de yield é semelhante à palavra-chave return , exceto que ela retornará um gerador . E o objeto gerador irá percorrer apenas uma vez .
produção tem dois benefícios:
Há outra explicação clara que talvez o ajude.
fonte