ti-enxame.com

Método ToList no Linq

Se eu não estiver errado, o método ToList () itera em cada elemento da coleção fornecida e os adiciona à nova instância de List e retorna esta instância. Suponha que um exemplo

//using linq
list = Students.Where(s => s.Name == "ABC").ToList();

//traditional way
foreach (var student in Students)
{
  if (student.Name == "ABC")
    list.Add(student);
}

Eu acho que a maneira tradicional é mais rápida, pois ela faz um loop apenas uma vez, onde como acima do Linq itera duas vezes uma vez para o método Where e depois para o método ToList ().

O projeto no qual estou trabalhando agora tem amplo uso de Listas e vejo que há muito desse tipo de uso de ToList () e outros métodos que podem ser melhorados como acima se eu pegar lista variável como IEnumerable e remova .ToList () e use-a posteriormente como IEnumerable.

Essas coisas têm algum impacto no desempenho?

13
user1976469

Essas coisas têm algum impacto no desempenho?

Isso depende do seu código. Na maioria das vezes, o uso do LINQ causa um pequeno impacto no desempenho. Em alguns casos, esse hit pode ser significativo para você, mas você deve evitar o LINQ apenas quando sabe que ele é muito lento para você (ou seja, se o perfil do seu código mostrou que o LINQ é o motivo do seu código ser lento).

Mas você está certo que usar ToList() com muita freqüência pode causar problemas de desempenho significativos. Você deve chamar ToList() apenas quando for necessário. Esteja ciente de que também há casos em que adicionar ToList() pode melhorar muito o desempenho (por exemplo, quando a coleção é carregada do banco de dados toda vez que é iterada).

Quanto ao número de iterações: depende do que exatamente você quer dizer com “itera duas vezes”. Se você contar o número de vezes que MoveNext() é chamado em alguma coleção, então sim, usando Where() desta forma leva à iteração duas vezes. A sequência de operações é assim (para simplificar, assumirei que todos os itens correspondem à condição):

  1. Where() é chamado, sem iteração por enquanto, Where() retorna um enumerável especial.
  2. ToList() é chamado, chamando MoveNext() no enumerável retornado de Where().
  3. Where() agora chama MoveNext() na coleção original e obtém o valor.
  4. Where() chama seu predicado, que retorna true.
  5. MoveNext() chamado de ToList() retorna, ToList() obtém o valor e o adiciona à lista.

O que isso significa é que se todos os n itens na coleção original corresponderem à condição, MoveNext() será chamado de 2n vezes, n vezes de Where() e n vezes de ToList().

11
svick
var list = Students.Where(s=>s.Name == "ABC"); 

Isso apenas criará uma consulta e não repetirá os elementos até que a consulta seja usada. Ao chamar ToList (), primeiro executará a consulta e, portanto, somente fará um loop em seus elementos uma vez.

List<Student> studentList = new List<Student>();
var list = Students.Where(s=>s.Name == "ABC");
foreach(Student s in list)
{
    studentList.add(s);
}

este exemplo também irá iterar apenas uma vez. Porque é usado apenas uma vez. Tenha em mente que a lista irá iterar todos os alunos sempre que for chamada. Não apenas aqueles cujos nomes são ABC. Uma vez que é uma consulta.

E para a discussão posterior, fiz um exemplo de teste. Talvez não seja a melhor implementação de IEnumable, mas ele faz o que deveria.

Primeiro temos nossa lista

public class TestList<T> : IEnumerable<T>
{
    private TestEnumerator<T> _Enumerator;

    public TestList()
    {
        _Enumerator = new TestEnumerator<T>();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _Enumerator;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    internal void Add(T p)
    {
        _Enumerator.Add(p);
    }
}

E como queremos contar quantas vezes MoveNext é chamado, temos que implementar nosso enumerador personalizado também. Observe em MoveNext que temos um contador que é estático em nosso programa.

public class TestEnumerator: IEnumerator {public Item FirstItem = null; Item público CurrentItem = null;

    public TestEnumerator()
    {
    }

    public T Current
    {
        get { return CurrentItem.Value; }
    }

    public void Dispose()
    {

    }

    object System.Collections.IEnumerator.Current
    {
        get { throw new NotImplementedException(); }
    }

    public bool MoveNext()
    {
        Program.Counter++;
        if (CurrentItem == null)
        {
            CurrentItem = FirstItem;
            return true;
        }
        if (CurrentItem != null && CurrentItem.NextItem != null)
        {
            CurrentItem = CurrentItem.NextItem;
            return true;
        }
        return false;
    }

    public void Reset()
    {
        CurrentItem = null;
    }

    internal void Add(T p)
    {
        if (FirstItem == null)
        {
            FirstItem = new Item<T>(p);
            return;
        }
        Item<T> lastItem = FirstItem;
        while (lastItem.NextItem != null)
        {
            lastItem = lastItem.NextItem;
        }
        lastItem.NextItem = new Item<T>(p);
    }
}

E então temos um item personalizado que envolve nosso valor

public class Item<T>
{
    public Item(T item)
    {
        Value = item;
    }

    public T Value;

    public Item<T> NextItem;
}

Para usar o código real, criamos uma "lista" com 3 entradas.

    public static int Counter = 0;
    static void Main(string[] args)
    {
        TestList<int> list = new TestList<int>();
        list.Add(1);
        list.Add(2);
        list.Add(3);

        var v = list.Where(c => c == 2).ToList(); //will use movenext 4 times
        var v = list.Where(c => true).ToList();   //will also use movenext 4 times


        List<int> tmpList = new List<int>(); //And the loop in OP question
        foreach(var i in list)
        {
            tmpList.Add(i);
        }                                    //Also 4 times.
    }

E conclusão? Como isso afeta o desempenho? O MoveNext é chamado n + 1 vezes neste caso. Independentemente de quantos itens temos. E também o WhereClause não importa, ele ainda executará MoveNext 4 vezes. Porque sempre executamos nossa consulta em nossa lista inicial. O único impacto de desempenho que sofreremos é a estrutura LINQ real e suas chamadas. Os loops reais feitos serão os mesmos.

E antes que alguém pergunte porque é N + 1 vezes e não N vezes. É porque ele retorna falso da última vez quando está sem elementos. Tornando-se o número de elementos + fim da lista.

5
Evelie

Para responder completamente, depende da implementação. Se você estiver falando sobre LINQ to SQL/EF, haverá apenas uma iteração neste caso quando .ToList é chamado, que chama internamente .GetEnumerator. A expressão de consulta é então analisada em TSQL e passada para o banco de dados. As linhas resultantes são então iteradas (uma vez) e adicionadas à lista.

No caso do LINQ to Objects, também há apenas uma passagem pelos dados. O uso de retorno de rendimento na cláusula where configura uma máquina de estado internamente que mantém o controle de onde o processo está na iteração. Onde NÃO faz uma iteração completa criando uma lista temporária e depois passando esses resultados para o resto da consulta. Ele apenas determina se um item atende a um critério e só passa aqueles que correspondem.

1
Jim Wooley

Em primeiro lugar, Por que você está me perguntando? Meça por você mesmo e veja.

Dito isso, Where, Select, OrderBy e os outros métodos de extensão LINQ IEnumerable, em geral, são implementados da forma mais lenta possível (o yield palavra-chave é usada com freqüência). Isso significa que eles não funcionam nos dados, a menos que seja necessário. Do seu exemplo:

var list = Students.Where(s => s.Name == "ABC");

não vai executar nada. Isso retornará momentaneamente, mesmo se Students for uma lista de 10 milhões de objetos. O predicado não será chamado até que o resultado seja realmente solicitado em algum lugar, e isso é praticamente o que ToList() faz: ele diz "Sim, os resultados - todos eles - são necessários imediatamente".

No entanto, há alguma sobrecarga inicial na chamada dos métodos LINQ, de modo que a forma tradicional será, em geral, mais rápida, mas a composibilidade e a facilidade de uso dos métodos LINQ, IMHO, mais do que compensam isso.

Se você gosta de dar uma olhada em como esses métodos são implementados, eles estão disponíveis para referência em Fontes de Referência da Microsoft .

1
SWeko