Na pergunta Como posso expor apenas um fragmento de IList <>, uma das respostas tinha o seguinte trecho de código:
IEnumerable<object> FilteredList()
{
foreach(object item in FullList)
{
if(IsItemInPartialList(item))
yield return item;
}
}
O que a palavra-chave yield faz lá? Eu já o vi referenciado em alguns lugares e em outra pergunta, mas ainda não descobri o que realmente faz. Estou acostumado a pensar em rendimento no sentido de um segmento ceder a outro, mas isso não parece relevante aqui.
Respostas:
A
yield
palavra-chave realmente faz bastante aqui.A função retorna um objeto que implementa a
IEnumerable<object>
interface. Se uma função de chamada começarforeach
sobre esse objeto, a função será chamada novamente até que "ceda". Este é o açúcar sintático introduzido no C # 2.0 . Nas versões anteriores, era necessário criar objetosIEnumerable
eIEnumerator
objetos próprios para fazer coisas assim.A maneira mais fácil de entender códigos como esse é digitar um exemplo, definir alguns pontos de interrupção e ver o que acontece. Tente seguir este exemplo:
Ao seguir o exemplo, você encontrará a primeira chamada de
Integers()
retorno1
. A segunda chamada retorna2
e a linhayield return 1
não é executada novamente.Aqui está um exemplo da vida real:
fonte
yield break;
quando não quiser mais retornar itens.yield
não é uma palavra-chave. Se fosse então não poderia usar rendimento como um identificador como emint yield = 500;
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'
. não parece certo para mim. Eu sempre pensei na palavra-chave c # yield no contexto de "a colheita produz uma colheita abundante", em vez de "o carro produz para o pedestre".Iteração. Ele cria uma máquina de estado "embaixo das cobertas" que lembra onde você estava em cada ciclo adicional da função e parte daí.
fonte
O rendimento tem dois grandes usos,
Ajuda a fornecer iteração personalizada sem criar coleções temporárias.
Ajuda a fazer iteração com estado.
Para explicar acima dois pontos mais demonstrativamente, eu criei um vídeo simples que você pode assistir aqui
fonte
yield
. Artigo do projeto de código de @ ShivprasadKoirala Qual é o uso do rendimento em C #? da mesma explicação é também uma boa fonteyield
é uma maneira "rápida" de criar um IEnumerator personalizado (em vez de uma classe implementar a interface do IEnumerator).Recentemente, Raymond Chen também publicou uma série interessante de artigos sobre a palavra-chave yield.
Embora seja nominalmente usado para implementar facilmente um padrão de iterador, pode ser generalizado em uma máquina de estado. Não faz sentido citar Raymond, a última parte também tem links para outros usos (mas o exemplo no blog de Entin é especialmente bom, mostrando como escrever código seguro assíncrono).
fonte
À primeira vista, o retorno de rendimento é um açúcar .NET para retornar um IEnumerable .
Sem rendimento, todos os itens da coleção são criados de uma vez:
Mesmo código usando yield, ele retorna item por item:
A vantagem de usar yield é que, se a função que consome seus dados simplesmente precisar do primeiro item da coleção, o restante dos itens não será criado.
O operador de rendimento permite a criação de itens conforme exigido. Essa é uma boa razão para usá-lo.
fonte
yield return
é usado com enumeradores. Em cada declaração de chamada de rendimento, o controle é retornado ao chamador, mas garante que o estado do chamado seja mantido. Devido a isso, quando o chamador enumera o próximo elemento, ele continua a execução no método chamado da instrução imediatamente após ayield
instrução.Vamos tentar entender isso com um exemplo. Neste exemplo, correspondendo a cada linha, mencionei a ordem na qual a execução flui.
Além disso, o estado é mantido para cada enumeração. Suponha que eu tenha outra chamada para o
Fibs()
método, em seguida, o estado será redefinido para ele.fonte
Intuitivamente, a palavra-chave retorna um valor da função sem deixá-lo, ou seja, no seu exemplo de código, ele retorna o
item
valor atual e, em seguida, retoma o loop. Mais formalmente, é usado pelo compilador para gerar código para um iterador . Iteradores são funções que retornamIEnumerable
objetos. O MSDN tem vários artigos sobre eles.fonte
Uma implementação de lista ou matriz carrega todos os itens imediatamente, enquanto a implementação de rendimento fornece uma solução de execução adiada.
Na prática, geralmente é desejável executar a quantidade mínima de trabalho conforme necessário para reduzir o consumo de recursos de um aplicativo.
Por exemplo, podemos ter um aplicativo que processa milhões de registros de um banco de dados. Os seguintes benefícios podem ser alcançados quando usamos IEnumerable em um modelo baseado em pull de execução adiada:
Aqui está uma comparação entre criar uma coleção primeiro, como uma lista em comparação ao uso de yield.
Exemplo de lista
Saída do console
ContactListStore: Criando contato 1
ContactListStore: Criando contato 2
ContactListStore: Criando contato 3
Pronto para percorrer a coleção.
Nota: A coleção inteira foi carregada na memória sem solicitar um único item na lista
Exemplo de rendimento
Saída do console
Pronto para percorrer a coleção.
Nota: A coleção não foi executada. Isso ocorre devido à natureza de "execução adiada" do IEnumerable. A construção de um item só ocorrerá quando for realmente necessário.
Vamos chamar a coleção novamente e reverter o comportamento quando buscarmos o primeiro contato na coleção.
Saída do console
Pronta para percorrer a coleção
ContactYieldStore: Criando contato 1
Olá Bob
Agradável! Somente o primeiro contato foi construído quando o cliente "retirou" o item da coleção.
fonte
Aqui está uma maneira simples de entender o conceito: A idéia básica é que, se você deseja usar uma coleção "
foreach
", mas reunir os itens na coleção é caro por algum motivo (como consultá-los em um banco de dados), E muitas vezes você não precisará da coleção inteira; em seguida, cria uma função que cria a coleção um item de cada vez e o devolve ao consumidor (que pode finalizar o esforço de coleta mais cedo).Pense da seguinte maneira: você vai ao balcão de carne e quer comprar um quilo de presunto fatiado. O açougueiro pega um presunto de 10 libras nas costas, coloca-o na máquina de cortar fatias, corta a coisa toda, depois traz a pilha de fatias de volta para você e mede uma libra. (À moda antiga). Com ele
yield
, o açougueiro leva a máquina de fatiar para o balcão e começa a fatiar e "ceder" cada fatia na balança até medir 1 libra e depois embrulhá-la para você e pronto. O Old Way pode ser melhor para o açougueiro (permite que ele organize suas máquinas da maneira que ele gosta), mas o New Way é claramente mais eficiente na maioria dos casos para o consumidor.fonte
A
yield
palavra-chave permite criar umIEnumerable<T>
no formulário em um bloco iterador . Esse bloco iterador suporta execução adiada e, se você não estiver familiarizado com o conceito, pode parecer quase mágico. No entanto, no final do dia, é apenas um código que é executado sem truques estranhos.Um bloco iterador pode ser descrito como açúcar sintático, em que o compilador gera uma máquina de estado que controla até que ponto a enumeração do enumerável progrediu. Para enumerar um enumerável, você costuma usar um
foreach
loop. No entanto, umforeach
loop também é açúcar sintático. Portanto, você tem duas abstrações removidas do código real, e é por isso que inicialmente pode ser difícil entender como tudo funciona em conjunto.Suponha que você tenha um bloco iterador muito simples:
Os blocos iteradores reais geralmente têm condições e loops, mas quando você verifica as condições e desenrola os loops, eles ainda acabam como
yield
instruções intercaladas com outro código.Para enumerar o bloco iterador, um
foreach
loop é usado:Aqui está a saída (sem surpresas aqui):
Como afirmado acima, o
foreach
açúcar sintático:Em uma tentativa de desembaraçar isso, criei um diagrama de sequência com as abstrações removidas:
A máquina de estado gerada pelo compilador também implementa o enumerador, mas para tornar o diagrama mais claro, eu os mostrei como instâncias separadas. (Quando a máquina de estado é enumerada de outro encadeamento, você obtém instâncias separadas, mas esse detalhe não é importante aqui.)
Toda vez que você chama seu bloco iterador, uma nova instância da máquina de estado é criada. No entanto, nenhum dos seus códigos no bloco iterador é executado até ser
enumerator.MoveNext()
executado pela primeira vez. É assim que a execução adiada funciona. Aqui está um exemplo (bastante bobo):Neste ponto, o iterador não foi executado. A
Where
cláusula cria um novoIEnumerable<T>
que agrupa oIEnumerable<T>
retornado por,IteratorBlock
mas esse enumerável ainda não foi enumerado. Isso acontece quando você executa umforeach
loop:Se você enumerar o enumerável duas vezes, uma nova instância da máquina de estado será criada a cada vez e seu bloco iterador executará o mesmo código duas vezes.
Note-se que os métodos LINQ gosto
ToList()
,ToArray()
,First()
,Count()
etc vai usar umforeach
circuito para enumerar o enumeráveis. Por exemploToList()
, enumerará todos os elementos do enumerável e os armazenará em uma lista. Agora você pode acessar a lista para obter todos os elementos do enumerável sem que o bloco iterador seja executado novamente. Existe uma troca entre usar a CPU para produzir os elementos do enumerável várias vezes e a memória para armazenar os elementos da enumeração para acessá-los várias vezes ao usar métodos comoToList()
.fonte
Se eu entendi isso corretamente, aqui está como eu fraseia isso da perspectiva da função implementando IEnumerable com yield.
fonte
A palavra-chave C # yield, para simplificar, permite muitas chamadas para um corpo de código, conhecido como iterador, que sabe retornar antes que seja concluído e, quando chamado novamente, continua onde parou - ou seja, ajuda um iterador torne-se transparente para cada item em uma sequência que o iterador retorna em chamadas sucessivas.
Em JavaScript, o mesmo conceito é chamado de Geradores.
fonte
É uma maneira muito simples e fácil de criar um enumerável para o seu objeto. O compilador cria uma classe que envolve seu método e que implementa, neste caso, IEnumerable <object>. Sem a palavra-chave yield, você teria que criar um objeto que implemente IEnumerable <object>.
fonte
Está produzindo uma enumerável sequência. Na verdade, o que ele faz é criar a sequência IEnumerable local e retorná-la como resultado do método
fonte
Este link tem um exemplo simples
Exemplos ainda mais simples estão aqui
Observe que o retorno do rendimento não retornará do método. Você pode até colocar um
WriteLine
após oyield return
O exemplo acima produz um IEnumerable de 4 ints 4,4,4,4
Aqui com um
WriteLine
. Ele adicionará 4 à lista, imprimirá abc, depois adicionará 4 à lista, concluirá o método e realmente retornará do método (depois que o método for concluído, como aconteceria com um procedimento sem retorno). Mas isso teria um valor, umaIEnumerable
lista deint
s, que ele retorna após a conclusão.Observe também que, quando você usa yield, o que você está retornando não é do mesmo tipo que a função. É do tipo de um elemento dentro da
IEnumerable
lista.Você usa yield com o tipo de retorno do método como
IEnumerable
. Se o tipo de retorno do método forint
orList<int>
e você usaryield
, ele não será compilado. Você pode usar oIEnumerable
tipo de retorno do método sem rendimento, mas parece que talvez não possa usar o rendimento sem oIEnumerable
tipo de retorno do método.E para executá-lo, é necessário chamá-lo de uma maneira especial.
fonte
public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }
epublic static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
yield return
muito bem (além da simples coisa que mencionei) e não o usei muito e não sei muito sobre seus usos, não acho que esse deva ser o aceito.Um ponto importante sobre a palavra-chave Yield é Lazy Execution . Agora, o que quero dizer com Lazy Execution é executar quando necessário. Uma maneira melhor de colocar isso é dando um exemplo
Exemplo: Não usar Rendimento, ou seja, Sem Execução Preguiçosa.
Exemplo: usando Yield, ou seja, Lazy Execution.
Agora, quando eu chamo os dois métodos.
você notará que os itens da lista terão 5 itens (passe o mouse sobre os itens da lista durante a depuração). Enquanto yieldItems terá apenas uma referência ao método e não aos itens. Isso significa que ele não executou o processo de obter itens dentro do método. Uma maneira muito eficiente de obter dados somente quando necessário. A implementação real do rendimento pode ser vista no ORM, como Entity Framework e NHibernate, etc.
fonte
Ele está tentando trazer alguma Ruby Goodness :)
Conceito: Este é um exemplo de código Ruby que imprime cada elemento da matriz
A implementação de cada método da matriz gera controle sobre o chamador (o 'puts x'), com cada elemento da matriz claramente apresentado como x. O chamador pode fazer o que for necessário com x.
No entanto, o .Net não chega até aqui. O C # parece ter associado o rendimento ao IEnumerable, de certa forma forçando você a escrever um loop foreach no chamador, como visto na resposta de Mendelt. Um pouco menos elegante.
fonte
yield
é associadoIEnumerable
e o C # não possui o conceito Ruby de "bloco". Mas o C # tem lambdas, o que poderia permitir a implementação de umForEach
método, muito parecido com o de Rubyeach
. Isso não significa que seria uma boa ideia fazê-lo .