Para que é a palavra-chave yield usada em C#?

na pergunta Como posso expor apenas um fragmento do IList<> uma das respostas tinha o seguinte excerto de código:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item )
            yield return item;
    }
}

O que é que a palavra-chave yield faz lá? Já o vi referenciado em alguns lugares, e outra pergunta, mas ainda não descobri o que ele realmente faz. Estou habituado a pensar em rendimento no sentido de um fio ceder a outro, mas isso não parece relevante aqui.

 674
Author: Community, 2008-09-02

16 answers

A palavra-chave yield faz muito aqui. A função retorna um objeto que implementa a interface Ienumerável. Se uma função de chamada começa a foreach-ing sobre este objeto, a função é chamada novamente até que "rende". Este é o açúcar sintático introduzido em C # 2.0. Em versões anteriores você teve que criar seus próprios objetos Ienumeradores e Ienumeradores para fazer coisas como essas.

A maneira mais fácil de entender um código como este é escrever um exemplo, definir alguns pontos de paragem e vê o que acontece.

Tenta ultrapassar isto por exemplo:

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

Quando passar pelo exemplo, irá encontrar a primeira chamada para o inteiros() devolve 1. A segunda chamada retorna 2 e a linha "yield return 1" não é executada novamente.

Aqui está um exemplo da vida real.
public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}
 596
Author: Mendelt, 2016-10-22 10:16:20
Iteração. Ele cria uma máquina de estado "sob as capas" que lembra onde você estava em cada ciclo adicional da função e pega a partir daí.
 327
Author: Joel Coehoorn, 2014-02-14 17:55:38

O rendimento tem duas grandes utilizações,

  1. Ele ajuda a fornecer iteração personalizada sem criar coleções temporárias.

  2. Ajuda a fazer uma iteração de Estado. enter image description here

Para explicar acima de dois pontos de forma mais demonstrativa, criei um vídeo simples que pode ver aqui.
 158
Author: Shivprasad Koirala, 2017-09-20 12:14:03
Recentemente, Raymond Chen também publicou uma interessante série de artigos sobre a palavra-chave yield. Enquanto é nominalmente usado para implementar facilmente um padrão iterador, mas pode ser generalizado em uma máquina de Estado. Não vale a pena citar Raymond, a última parte também liga para outros usos (mas o exemplo no blog de Entin é bom esp, mostrando como escrever código seguro async).
 124
Author: Svend, 2014-11-27 08:31:18
À primeira vista, o rendimento é um açúcar líquido para devolver um Ienumerável.

Sem rendimento, todos os itens da colecção são criados de uma só vez:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

O mesmo código usando o yield, devolve item por item:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

A vantagem de usar yield é que se a função que consome os seus dados simplesmente precisa do primeiro item da coleção, o resto dos itens não serão criados.

O operador de rendimento permite a criação de itens como é exigido. Isso é bom. razão para usá-lo.

 50
Author: Marquinho Peli, 2018-02-22 16:09:30

yield return é utilizado com enumeradores. Em cada chamada de declaração de rendimento, o controle é devolvido ao chamador, mas garante que o estado do callee é mantido. Devido a isso, quando o chamador enumera o próximo elemento, ele continua a execução no método de callee a partir da declaração imediatamente após a Declaração yield.

Vamos tentar entender isso com um exemplo. Neste exemplo, correspondente a cada linha eu mencionei a ordem em que a execução fluxo.
static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

Também, o estado é mantido para cada enumeração. Suponha que tenho outra chamada para o método Fibs() então o estado será reiniciado para ele.

 29
Author: RKS, 2016-11-18 07:53:17

Intuitivamente, a palavra-chave devolve um valor da função sem a deixar, ou seja, no seu exemplo de código, devolve o valor actual item e depois retoma o ciclo. Mais formalmente, é usado pelo compilador para gerar código para um iterator . Iteradores são funções que devolvem IEnumerable objectos. O MSDN tem vários artigos sobre eles.

 26
Author: Konrad Rudolph, 2008-09-02 13:19:09

Uma lista ou uma implementação de array carrega todos os itens imediatamente, enquanto a implementação yield fornece uma solução de execução diferida.

Na prática, é frequentemente desejável realizar a quantidade mínima de trabalho necessária para reduzir o consumo de recursos de uma aplicação. Por exemplo, podemos ter uma aplicação que processa milhões de registos de uma base de dados. Os seguintes benefícios podem ser alcançados quando usamos IEnumerable em uma execução diferida modelo baseado em tracção:
  • é provável que a escalabilidade, fiabilidade e previsibilidade melhorem, uma vez que o número de registos não afecta significativamente as necessidades de recursos da aplicação.
  • é provável que o desempenho e a capacidade de resposta melhorem uma vez que o processamento pode começar imediatamente em vez de esperar que toda a colecção seja carregada em primeiro lugar.
  • é provável que a recuperabilidade e a utilização melhorem desde o pedido. pode ser parado, iniciado, interrompido ou falhar. Apenas os itens em progresso serão perdidos em comparação com a pré-obtenção de todos os dados, onde apenas usando uma parte dos resultados foi realmente usado.
  • o processamento contínuo é possível em ambientes onde se adicionam fluxos de carga de trabalho constantes.

Aqui está uma comparação entre construir uma coleção primeiro, como uma lista, em comparação com o uso de yield.

Exemplo Da Lista

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

consola Resultado
ContactListStore: a criar o contacto 1
ContactListStore: a criar o contacto 2
ContactListStore: criar o contacto 3
Pronto para iterar através da colecção.

Nota: toda a colecção foi carregada em memória sem sequer pedir um único item na lista

Exemplo De Rendimento

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

Resultado Da Consola
Pronto para iterar através da colecção.

Nota: A Colecção não era executado de todo. Isto é devido à natureza "execução diferida" de Ienumerável. A construção de um item só ocorrerá quando for realmente necessário. Vamos chamar a colecção de novo e inverter o comportamento quando tivermos o primeiro contacto NA colecção.
static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

Resultado Da Consola
Pronto para iterar através da colecção
ContactYieldStore: criar contacto 1
Olá, Bob. Boa! Apenas o primeiro contacto foi construído quando o o cliente "tirou" o item da colecção.

 18
Author: Strydom, 2015-12-11 22:12:00
Aqui está uma maneira simples de entender o conceito: A ideia básica é que, se quiser uma colecção em que possa usar "foreach", mas a recolha dos itens na colecção é cara por alguma razão (como pesquisá-los numa base de dados), e muitas vezes não irá precisar de toda a colecção, então irá criar uma função que constrói a colecção um item de cada vez e devolve-o ao consumidor (que poderá então terminar o esforço da colecção mais cedo).

Pensa nisto. vai ao balcão da carne e compra meio quilo de fiambre fatiado. O carniceiro leva um presunto de 10 libras para trás, coloca-o na máquina de cortar, corta tudo, depois traz a pilha de fatias de volta para ti e mede um quilo dela. (Velha maneira). Com {[[1]}, o talhante traz a máquina de cortar para o balcão, e começa a cortar e" ceder " cada fatia para a balança até que ele mede 1 libra, em seguida, enrolá-lo para você e você está feito. O velho caminho pode ser melhor para o carniceiro (deixa-o organizar as suas máquinas da maneira que ele gosta), mas a nova maneira é claramente mais eficiente na maioria dos casos para o consumidor.

 13
Author: kmote, 2016-09-01 18:16:25

A palavra-chave yield permite-lhe criar um IEnumerable<T> na forma em um bloco iterador. Este bloco iterator suporta execução diferida {[[30]} e se você não está familiarizado com o conceito pode parecer quase mágico. No entanto, no final de contas, é apenas um código que executa sem truques estranhos.

Um bloco iterador pode ser descrito como açúcar sintático onde o compilador gera uma máquina de Estado que mantém o controle de quão longe a enumeração dos enumeráveis progrediu. Para enumerar um enumerável, você frequentemente usa um laço foreach. No entanto, um laço foreach também é açúcar sintático. Então vocês são duas abstrações removidas do Código real, e é por isso que inicialmente pode ser difícil entender como tudo funciona em conjunto.

Assume que tens um bloco iterador muito simples:

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

Blocos iteradores reais muitas vezes têm condições e loops, mas quando você verificar as condições e desenrolar os loops eles ainda acabam como yield declarações intercaladas com outro código.

Para enumerar o bloco iterador a foreach, utiliza-se o loop:

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

Aqui está a saída (sem surpresas aqui):

Begin
1
After 1
2
After 2
42
End

Tal como indicado acima foreach é açúcar sintático:

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

Numa tentativa de desvendar isto, criei um diagrama de sequência com as abstracções removidas:

C# iterator block sequence diagram

A máquina de Estado gerada pelo compilador também implementa o enumerador, mas para tornar o diagrama mais claro Mostrei-os como exemplos separados. (Quando a máquina de Estado é enumerada a partir de outro tópico você realmente obter instâncias separadas, mas esse detalhe não é importante aqui.)

Cada vez que você chama o seu iterator bloqueia uma nova instância da máquina do Estado é criada. No entanto, nenhum do seu código no bloco iterator é executado até enumerator.MoveNext() executar pela primeira vez. É assim que a execução adiada funciona. Aqui está um exemplo (bastante tolo):
var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

Neste momento, o iterator não executou. A cláusula Where cria um novo {[7] } que envolve o IEnumerable<T> devolvido por IteratorBlock mas este enumerável ainda tem de ser enumerado. Isto acontece quando se executa um ciclo foreach:

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

Se você enumerar o enumerável duas vezes, então uma nova instância da máquina de Estado é criada de cada vez e seu bloco iterador irá executar o mesmo código duas vezes.

Reparem que métodos LINQ como ToList(), ToArray(), First(), Count() etc. irá usar um laço foreach para enumerar o enumeravel. Por exemplo ToList() irá enumerar todos os elementos do enumerável e armazená-los em uma lista. Agora você pode acessar a lista para obter todos os elementos do enumerável sem que o bloco iterator execute novamente. Há um trade-off entre usar CPU para produzir os elementos do enumerável múltiplas vezes e memória para armazenar os elementos da enumeração para acessá-los várias vezes ao usar métodos como ToList().
 10
Author: Martin Liversage, 2017-06-28 11:54:13

A Palavra-chave C# yield, para ser mais simples, permite muitas chamadas para um corpo de código, referido como iterador, que sabe como retornar antes de ser feito e, quando chamado novamente, continua onde parou - ou seja, ajuda um iterador a tornar-se claramente stateful por cada item em uma sequência que o iterator retorna em chamadas sucessivas.

Em JavaScript, o mesmo conceito é chamado de geradores.

 9
Author: BoiseBaked, 2017-10-01 15:58:12
É uma maneira muito simples e fácil de criar um enumerável para o seu objeto. O compilador cria uma classe que envolve o seu método e que implementa, neste caso, Ienumerável. Sem a palavra-chave yield, você teria que criar um objeto que implementa IEnumerable.
 5
Author: Will, 2008-09-02 13:17:36
Está a produzir uma sequência enumerável. O que ele faz é realmente criar uma sequência Ienumerável local e devolvê-la como resultado do método
 3
Author: aku, 2008-09-02 13:18:25

Este link tem um exemplo simples

Exemplos ainda mais simples estão aqui

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

Notem que o rendimento não voltará do método. Você pode até colocar um WriteLine Depois do yield return

O acima produz um IEnumerable de 4 ints 4,4,4,4

Aqui com um. Irá adicionar 4 à lista, imprimir abc, em seguida, adicionar 4 à lista, em seguida, completar o método e então realmente voltar do método(uma vez que o método tenha concluído, como aconteceria com um procedimento sem retorno). Mas isto teria um valor, uma lista IEnumerable de ints, que retorna após a conclusão.
public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

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 elemento dentro da lista IEnumerable.

Usa o yield com o tipo de retorno do método como IEnumerable. Se o tipo de retorno do método for int ou List<int> e você usar yield, Então ele não irá compilar. Pode utilizar IEnumerable método de devolução SEM rendimento mas parece que talvez você não possa usar yield sem o tipo de retorno do método IEnumerable.

E para fazê-lo executar, tem de chamá-lo de uma forma especial.
static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}
 1
Author: barlop, 2016-11-18 07:51:35

Se eu entender isto corretamente, aqui está como eu diria isto a partir da perspectiva da função implementando Ienumerável com yield.

    Aqui está uma. Liga outra vez se precisares de outra. Vou lembrar-me do que já te dei. Só saberei se te posso dar outra quando voltares a ligar.
 1
Author: Casey, 2018-04-15 22:28:47
Está a tentar trazer alguma bondade Rubi.:)
conceito: Este é um código de Ruby de amostra que imprime cada elemento da matriz
 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

A Matriz de cada implementação do método produz o controle sobre o autor da chamada (o 'coloca-x') com cada elemento da matriz fossem apresentados como x. O chamador pode, então, fazer tudo o que ele precisa fazer com x.

No entanto . Net não vai até aqui.. C # parece ter acoplado o rendimento com IEnumerable, de certo modo forçando você a escrever um loop foreach no chamador como visto na resposta de Mendelt. Um pouco menos elegante.

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}
 -2
Author: Gishu, 2008-09-02 14:06:52