Pergunta

No Como Posso Expor Somente um Fragmento de IList<> pergunta uma das respostas tinham o seguinte trecho de código:

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

O que faz o rendimento de palavras-chave fazer o que lá?Eu já vi isso referenciado em alguns lugares, e uma outra pergunta, mas eu ainda não descobri o que ele realmente faz.Eu estou acostumado a pensar de rendimento, no sentido de uma thread ceder à outra, mas que não parece relevante aqui.

Foi útil?

Solução

O yield palavra-chave, na verdade, faz um monte aqui.

A função retorna um objeto que implementa a IEnumerable<object> interface.Se uma função de chamada começa foreaching sobre este objeto, a função é chamada novamente até que ele "produz".Este é um açúcar sintático introduzida em C# 2.0.Em versões anteriores, você teve para criar o seu próprio IEnumerable e IEnumerator objetos para fazer coisas como esta.

A maneira mais fácil compreender o código como este é escrever-em um exemplo, definir alguns pontos de interrupção e ver o que acontece.Tente executar esse 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 você passo a passo através do exemplo, você vai encontrar a primeira chamada para Integers() retorna 1.A segunda chamada retorna 2 e a linha de yield return 1 não é executado 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);
                }
            }
        }
    }
}

Outras dicas

Iteração.Ele cria uma máquina de estado "nos bastidores" que se lembra de onde você estava em cada ciclo a função e a pega de lá.

Rendimento tem duas grandes utilizações,

  1. Ela ajuda a fornecer a iteração sem criar temp coleções.

  2. Ele ajuda a fazer com monitoração de estado iteração.enter image description here

A fim de explicar acima, dois pontos a mais demonstratively, eu criei um vídeo simples, você pode vê-lo aqui

Recentemente, Raymond Chen também fez uma interessante série de artigos sobre o rendimento de palavras-chave.

Embora nominalmente usado para facilmente implementar um iterador padrão, mas pode ser generalizada em uma máquina de estado.Nenhum ponto na citando Raymond, a última parte, também, links para outros usos (mas o exemplo em Entin o blog é bom controlo electrónico de velocidade, mostrando como escrever assíncrono código de segurança).

À primeira vista, o rendimento de retorno é um .NET açúcar para retornar um IEnumerable.

Sem rendimento, todos os itens da coleçã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()
        };
    }
}

Mesmo código utilizando rendimento, ela retorna, 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 o rendimento é de que se a função de consumir os seus dados, basta que o primeiro item da coleção, o resto dos itens não será criado.

O rendimento do operador permite a criação de itens, como é exigido.Essa é uma boa razão para usá-lo.

yield return é usado com enumeradores.Em cada chamada de rendimento instrução, o controle é retornado ao chamador, mas ele garante que o receptor do estado seja mantida.Devido a isso, quando o chamador enumera o próximo elemento, ele continua a execução o destinatário da chamada de método de instrução imediatamente após o yield instrução.

Vamos tentar entender isso com um exemplo.Neste exemplo, correspondente a cada linha de eu ter mencionado a ordem de execução de fluxos.

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
    }
}

Além disso, o estado é mantido para cada enumeração.Suponha que, eu tenho uma outra chamada para Fibs() método em seguida, o estado será redefinido para ele.

Intuitivamente, a palavra-chave que retorna um valor da função, sem sair, i.é.no código de exemplo devolve o atual item do valor e, em seguida, recomeça o ciclo.Mais formalmente, ele é usado pelo compilador para gerar código para uma iterador.Iteradores são funções que retornam IEnumerable os objetos.O MSDN tem vários artigos sobre eles.

Uma lista ou matriz de implementação carrega todos os itens imediatamente considerando que o rendimento de implementação fornece um diferidos de execução da solução.

Na prática, muitas vezes é desejável para executar a quantidade mínima de trabalho, conforme necessário, a fim de reduzir o consumo de recursos de uma aplicação.

Por exemplo, podemos ter uma aplicação que o processo de milhões de registros de um banco de dados.Os seguintes benefícios podem ser alcançados quando usamos IEnumerable em um diferidos de execução de puxar baseado no modelo:

  • A escalabilidade, a confiabilidade e a previsibilidade são susceptíveis de melhorar uma vez que o número de registros de não afetar significativamente o recurso da aplicação de requisitos.
  • Desempenho e capacidade de resposta são susceptíveis de melhorar, pois o tratamento pode começar imediatamente em vez de aguardar a coleção inteira para ser carregado pela primeira vez.
  • Valorização e utilização são susceptíveis de melhorar, pois o aplicativo pode ser parado, iniciado, interrompido ou não.Apenas os itens na barra de progresso será perdido em relação ao pré-obtenção de todos os dados, onde o uso de apenas uma parte dos resultados foi realmente utilizado.
  • Processamento contínuo é possível, em ambientes onde a carga de trabalho constante fluxos são adicionados.

Aqui está uma comparação entre a construir uma coleção de primeira, como uma lista em comparação ao uso de rendimento.

Exemplo De 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();
    }

Saída Do Console
ContactListStore:A criação de contacto 1
ContactListStore:A criação de contacto 2
ContactListStore:A criação de contatos 3
Pronto para iterar na coleção.

Nota:Toda a coleção foi carregado na memória, mesmo sem pedir um único item na lista

Rendimento Exemplo

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();
}

Saída Do Console
Pronto para iterar na coleção.

Nota:A coleção não foi executada.Isto é devido à "execução retardada" natureza de IEnumerable.A construção de um item só irá ocorrer quando é realmente necessário.

Vamos chamar o conjunto novamente e anverso o comportamento quando devemos buscar o primeiro contato na coleçã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();
}

Saída Do Console
Pronto para iterar na coleção
ContactYieldStore:A criação de contacto 1
Olá Bob

Bom!Apenas o primeiro contato foi construído quando o cliente "puxou" o item da coleção.

Aqui está uma maneira simples para entender o conceito:A idéia básica é que, se você quiser uma coleção que você pode usar "foreach"no, mas recolhendo os itens para a coleção é caro, por algum motivo (como consulta-los para fora de um banco de dados), E muitas vezes você não precisa de toda a coleção, em seguida, você criar uma função que cria a coleção de um item em uma hora e rende-se de volta para o consumidor (que pode, em seguida, encerrar a coleta de esforço precoce).

Pense nisso desta maneira: Você vá para o balcão de carnes e quer comprar um quilo de presunto fatiado.O açougueiro 10 quilos de presunto para trás, coloca-o na segmentação de dados, máquina de fatias a coisa toda, em seguida, traz a pilha de fatias de volta para você e medidas de um quilo de ti.(Caminho VELHO).Com yield, o açougueiro traz o cortador máquina para o contador, e começa a fatiar e "rendimento" de cada fatia sobre a balança, até que ele mede de 1 libra, em seguida, envolve-lo para você e você está feito. A Velha Forma poderá ser melhor para o açougueiro (permite-lhe organizar a sua maquinaria do jeito que ele gosta), mas a Nova Forma é claramente o mais eficiente na maioria dos casos para o consumidor.

O yield palavra-chave permite que você crie um IEnumerable<T> na forma em um o iterador de bloco.Este iterador bloco suporta adiada a execução de 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 qualquer estranho truques.

Um iterador bloco pode ser descrito como um açúcar sintático onde o compilador gera uma máquina de estados que controla o quão longe a enumeração dos enumerável progrediu.Para enumerar um enumerable, muitas vezes você usar um foreach loop.No entanto, uma foreach loop também é um açúcar sintático.Então, são duas abstrações removido do código real que é por isso que, inicialmente, pode ser difícil entender como tudo funciona em conjunto.

Suponha que você tenha um muito simples iterador bloco:

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");
}

Real iterador blocos, muitas vezes, têm condições e ciclos, mas quando você verificar as condições e desenrolar-se os laços que eles ainda acabam yield instruções intercalado com outro código.

Para enumerar o iterador bloco a foreach o loop é usado:

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

Aqui é a saída (sem surpresas aqui):

Begin
1
After 1
2
After 2
42
End

Como dito acima foreach é um 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();
}

Em uma tentativa de desvendar esse eu ter criado um diagrama de sequência com as abstrações removido:

C# iterator block sequence diagram

A máquina do estado gerado pelo compilador também implementa o enumerador, mas para tornar o diagrama mais claro que eu mostraram-los como instâncias separadas.(Quando a máquina do estado é enumerada a partir de outro thread, você realmente obter instâncias separadas, mas esse detalhe não é importante aqui.)

Cada vez que você chamar o iterador de bloco de uma nova instância da máquina de estado é criado.No entanto, nenhum de seus códigos no iterador bloco é executado até que enumerator.MoveNext() executado pela primeira vez.Este é como o adiamento da execução de obras.Aqui é um pouco bobo) exemplo:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

Neste ponto, o iterador não foi executado.O Where cláusula cria um novo IEnumerable<T> que envolve o IEnumerable<T> devolvido pelo IteratorBlock mas este enumerável ainda tem que ser enumerados.Isso acontece quando você executar um foreach loop:

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

Se você enumerar as imensas duas vezes, em seguida, uma nova instância da máquina de estado é criado a cada momento e o iterador bloco irá executar o mesmo código duas vezes.

Repare que métodos como o LINQ ToList(), ToArray(), First(), Count() etc.vai usar um foreach loop para enumerar as imensas.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 a iteração do bloco de executar novamente.Há um trade-off entre o uso de CPU para produzir os elementos do enumerável várias vezes e memória para armazenar os elementos da enumeração, para acessá-los várias vezes quando usando métodos como ToList().

Se eu entendi corretamente, veja como eu iria dizer a partir da perspectiva da função de execução de IEnumerable com o rendimento.

  • Aqui está um.
  • Chamada novamente se você precisa de outro.
  • Eu vou lembrar o que eu já lhe deu.
  • Eu só vou saber se eu posso dar-lhe outra quando você ligar de novo.

A C# rendimento de palavras-chave, para colocá-lo simplesmente, permite muitas chamadas para um corpo de código, conhecido como um iterador, que sabe como voltar antes do que ele é feito e, quando chamado novamente, continua de onde parou - i.e.ele ajuda um iterador se tornam, de forma transparente, com monitoração de estado por cada item em uma sequência que o iterator retorna em chamadas sucessivas.

Em JavaScript, o mesmo conceito é chamado de Geradores.

É uma maneira muito simples e fácil para criar um enumerable para o seu objeto.O compilador cria uma classe que encapsula o seu método e que implementa, neste caso, IEnumerable<object>.Sem o rendimento de palavras-chave, você teria que criar um objeto que implementa IEnumerable<object>.

Ele está produzindo seqüência enumerável.O que ele faz é, na verdade, na criação de locais de IEnumerable sequência e retorná-lo como um resultado do método de

Este link tem um exemplo simples

Mesmo os exemplos mais simples são aqui

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

Observe que o rendimento de retorno não retorno do método.Você pode até mesmo colocar um WriteLine após o yield return

Acima produz um IEnumerable, de 4 de ints 4,4,4,4

Aqui com um WriteLine.Irá adicionar 4 para a lista, imprima abc, em seguida, adicione 4 para a lista e, em seguida, concluir o método e, assim, o retorno do método(uma vez que o método foi concluído, como aconteceria com um processo sem retorno).Mas isso teria um valor, uma IEnumerable lista de ints, que retorna após a sua conclusão.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

Observe também que quando você usa o rendimento, o que você está voltando não é do mesmo tipo da função.É o tipo de um elemento dentro do IEnumerable a lista.

Você usa o rendimento, com o retorno do método de tipo IEnumerable.Se o tipo de retorno do método é int ou List<int> e você usa yield, então ele não vai compilar.Você pode usar IEnumerable método tipo de retorno sem rendimento, mas parece que, talvez, você não pode usar sem rendimento IEnumerable método tipo de retorno.

E para obtê-lo para executar você tem que chamá-lo de uma maneira 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");
}

Ele está tentando trazer um pouco de Ruby Bondade :)
Conceito: Este é um exemplo de Ruby Código 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 método de implementação rendimentos o controle para o chamador (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 vá todo o caminho até aqui..C# parece ter juntamente rendimento, com IEnumerable, em uma maneira de forçá-lo a escrever um loop foreach no chamador, como visto no Mendelt resposta.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]; 
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top