C# Linq中的ToList方法

C# Linq中的ToList方法,c#,performance,linq,C#,Performance,Linq,如果我没有记错的话,ToList()方法将迭代所提供集合的每个元素,并将它们添加到列表的新实例中,然后返回该实例 //using linq list = Students.Where(s => s.Name == "ABC").ToList(); //traditional way foreach (var student in Students) { if (student.Name == "ABC") list.Add(student); } 我认为传统的方法速度更快,

如果我没有记错的话,ToList()方法将迭代所提供集合的每个元素,并将它们添加到列表的新实例中,然后返回该实例

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

//traditional way
foreach (var student in Students)
{
  if (student.Name == "ABC")
    list.Add(student);
}
我认为传统的方法速度更快,因为它只循环一次,从上面的Linq开始,对where方法迭代两次,然后对ToList()方法迭代一次

我现在正在从事的项目广泛使用列表,我看到有很多这样的使用ToList()和其他方法,如果我将list变量作为IEnumerable并删除.ToList(),并将其作为IEnumerable进一步使用,它们可以变得更好

这些事情对性能有影响吗

var list = Students.Where(s=>s.Name == "ABC"); 
这只会创建一个查询,在使用查询之前不会循环元素。通过调用ToList()将首先执行查询,从而只循环元素一次

List<Student> studentList = new List<Student>();
var list = Students.Where(s=>s.Name == "ABC");
foreach(Student s in list)
{
    studentList.add(s);
}
List studentList=newlist();
var list=Students.Where(s=>s.Name==“ABC”);
foreach(列表中的学生)
{
学生名单。添加;
}
这个例子也只会迭代一次。因为它只使用过一次。请记住,该列表将在每次调用时迭代所有学生。。不仅仅是那些名字叫ABC的人。因为这是一个疑问

在后面的讨论中,我做了一个测试示例。也许这不是IEnumable的最佳实现,但它做了它应该做的事情

首先我们有我们的名单

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);
    }
}
公共类测试列表:IEnumerable
{
专用测试器分子_枚举器;
公共测试列表()
{
_枚举数=新的TestEnumerator();
}
公共IEnumerator GetEnumerator()
{
返回_枚举器;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
抛出新的NotImplementedException();
}
内部无效添加(TP)
{
_枚举器。添加(p);
}
}
由于我们想计算调用MoveNext的次数,我们还必须实现自定义枚举器。在MoveNext中观察我们的程序中有一个静态计数器

公共类测试器分子:IEnumerator { 公共项FirstItem=null; 公共项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);
    }
}
公共分子()
{
}
公共电流
{
获取{return CurrentItem.Value;}
}
公共空间处置()
{
}
对象System.Collections.IEnumerator.Current
{
获取{抛出新的NotImplementedException();}
}
公共图书馆
{
程序计数器++;
如果(CurrentItem==null)
{
CurrentItem=第一项;
返回true;
}
if(CurrentItem!=null&&CurrentItem.NextItem!=null)
{
CurrentItem=CurrentItem.NextItem;
返回true;
}
返回false;
}
公共无效重置()
{
CurrentItem=null;
}
内部无效添加(TP)
{
if(FirstItem==null)
{
第一项=新项(p);
返回;
}
项目lastItem=第一个项目;
while(lastItem.NextItem!=null)
{
lastItem=lastItem.NextItem;
}
lastItem.NextItem=新项目(p);
}
}
然后,我们有一个自定义项目,只是包装我们的价值

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

    public T Value;

    public Item<T> NextItem;
}
公共类项目
{
公共项目(T项目)
{
价值=项目;
}
公共价值观;
公共项目NextItem;
}
为了使用实际代码,我们创建了一个包含3个条目的“列表”

    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.
    }
公共静态int计数器=0;
静态void Main(字符串[]参数)
{
TestList=新的TestList();
增加第(1)款;
增加(2);
增加(3);
var v=list.Where(c=>c==2.ToList();//将使用movenext 4次
var v=list.Where(c=>true).ToList();//还将使用movenext 4次
List tmpList=new List();//和OP问题中的循环
foreach(列表中的变量i)
{
tmpList.添加(i);
}//也是4次。
}
结论呢?它如何影响性能? 在这种情况下,MoveNext调用n+1次。不管我们有多少物品。 而且where子句也不重要,他仍将在接下来的4次竞选中获胜。因为我们总是在初始列表上运行查询。 我们将受到的唯一性能影响是实际的LINQ框架及其调用。实际制作的回路将是相同的

在有人问为什么是N+1次而不是N次之前。这是因为他上次在元素之外时返回false。使其成为元素数+列表末尾

这些事情对性能有影响吗

var list = Students.Where(s=>s.Name == "ABC"); 
这取决于你的代码。大多数情况下,使用LINQ确实会对性能造成小的影响。在某些情况下,这种影响可能对您很重要,但只有当您知道LINQ对您来说太慢时(即,如果对代码进行分析表明LINQ是代码慢的原因),才应该避免使用LINQ

但您是对的,过于频繁地使用
ToList()
可能会导致严重的性能问题。只有在必要时才应调用
ToList()
。请注意,在某些情况下,添加
ToList()
可以大大提高性能(例如,每次迭代从数据库加载集合时)

关于迭代次数:这取决于“迭代两次”的确切含义。如果您计算对某个集合调用
MoveNext()
的次数,那么是的,以这种方式使用
Where()
会导致迭代两次。操作的顺序是这样的(为了简化,我将假设所有项都符合条件):

  • Where()
    被调用,目前没有迭代,
    Where()
    返回一个特殊的可枚举项
  • 调用
    ToList()
    ,对从
    Where()
    返回的可枚举项调用
    MoveNext()
  • <