C# 迭代集合时IEnumerable vs List
我的问题基本上是什么是好的编程实践。在IEnumerable的情况下,每个项的计算时间与ToList的情况相同,整个集合在开始for循环之前进行迭代。 根据下面的代码,应该使用哪个函数(GetBool1 vs GetBool2)以及为什么C# 迭代集合时IEnumerable vs List,c#,C#,我的问题基本上是什么是好的编程实践。在IEnumerable的情况下,每个项的计算时间与ToList的情况相同,整个集合在开始for循环之前进行迭代。 根据下面的代码,应该使用哪个函数(GetBool1 vs GetBool2)以及为什么 public class TestListAndEnumerable1 { public static void Test() { GetBool1(); GetBool2(); Console
public class TestListAndEnumerable1
{
public static void Test()
{
GetBool1();
GetBool2();
Console.ReadLine();
}
private static void GetBool1()
{
var list = new List<int> {0,1,2,3,4,5,6,7,8,9};
foreach (var item in list.Where(PrintAndEvaluate))
{
Thread.Sleep(1000);
}
}
private static bool PrintAndEvaluate(int x)
{
Console.WriteLine("Hi from " + x);
return x%2==0;
}
private static void GetBool2()
{
List<int> list = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach (var item in list.Where(PrintAndEvaluate).ToList())
{
Thread.Sleep(1000);
}
}
}
公共类TestListAndEnumerable1
{
公共静态无效测试()
{
GetBool1();
GetBool2();
Console.ReadLine();
}
私有静态void GetBool1()
{
var list=新列表{0,1,2,3,4,5,6,7,8,9};
foreach(列表中的var项目,其中(打印和评估))
{
睡眠(1000);
}
}
私有静态布尔打印和评估(int x)
{
Console.WriteLine(“Hi from”+x);
返回x%2==0;
}
私有静态void GetBool2()
{
列表=新列表{0,1,2,3,4,5,6,7,8,9};
foreach(list.Where(printandeevaluate.ToList()中的变量项)
{
睡眠(1000);
}
}
}
这两个循环的性质不同。在第一种情况下,在迭代和计算每个项时,控制台将被写入,并且每个Console.Write之间将发生休眠
在第二种情况下,控制台写入操作也将被评估,但这些评估都将在休眠之前进行-只有在所有printandeevaluate调用完成时才会进行
第二种情况是两次枚举列表的成员,同时分配和分割内存
如果你的问题是“哪种方法最有效”,那么答案就是第一个例子,但是如果你想知道“还有其他更有效的方法吗”,那么就使用一个循环
for(int counter = 0 ; counter <= list.Count; counter ++)
{
if(PrintAndEvaluate(list[counter]))
{
Thread.Sleep(1000);
}
}
应使用(int counter=0;counter
GetBool1
的for
这两种方法之间唯一的区别是存在ToList()
调用,对吗
让我们先看一下ToList
调用的意义:
从IEnumerable
创建列表
这意味着在调用ToList
时将创建一个新列表。您可能知道,创建一个新列表需要时间和内存
另一方面,GetBool1
没有ToList
调用,因此它不需要花费太多的时间来执行。GetBool1是更好的选择。对于选项2,即使您将IEnumerable转换为List,当您再次调用foreach时,它也会调用GetEnumerator。但是差别非常小。我对您做了一点修改用于输出执行时间的r代码:
public static void Test()
{
var list = new List<int>();
for (int i = 0; i < 10000; i++)
{
list.Add(i);
}
GetBool1(list);
GetBool2(list);
GetBool3(list);
Console.ReadLine();
}
private static void GetBool1(List<int> list)
{
System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch();
watcher.Start();
foreach (var item in list.Where(PrintAndEvaluate))
{
Thread.Sleep(1);
}
watcher.Stop();
Console.WriteLine("GetBool1 - {0}", watcher.ElapsedMilliseconds);
}
private static bool PrintAndEvaluate(int x)
{
return x % 2 == 0;
}
private static void GetBool2(List<int> list)
{
System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch();
watcher.Start();
foreach (var item in list.Where(PrintAndEvaluate).ToList())
{
Thread.Sleep(1);
}
watcher.Stop();
Console.WriteLine("GetBool2 - {0}", watcher.ElapsedMilliseconds);
}
公共静态无效测试()
{
var list=新列表();
对于(int i=0;i<10000;i++)
{
列表.添加(i);
}
GetBool1(列表);
GetBool2(列表);
GetBool3(列表);
Console.ReadLine();
}
私有静态void GetBool1(列表)
{
System.Diagnostics.Stopwatch watcher=新的System.Diagnostics.Stopwatch();
watcher.Start();
foreach(列表中的var项目,其中(打印和评估))
{
睡眠(1);
}
watcher.Stop();
WriteLine(“GetBool1-{0}”,watcher.elapsedmillesons);
}
私有静态布尔打印和评估(int x)
{
返回x%2==0;
}
私有静态void GetBool2(列表)
{
System.Diagnostics.Stopwatch watcher=新的System.Diagnostics.Stopwatch();
watcher.Start();
foreach(list.Where(printandeevaluate.ToList()中的变量项)
{
睡眠(1);
}
watcher.Stop();
WriteLine(“GetBool2-{0}”,watcher.elapsedmillesons);
}
输出为:
GetBool1():创建一个枚举器并循环,返回匹配结果并运行代码块。GetBool2():创建一个枚举器返回匹配结果,将匹配结果复制到新列表(分配更多内存!如果列表是值类型列表,可能会有很多内存!),创建枚举数并循环,然后运行代码块。您认为哪一个更好?有关差异的更多信息,请查看…?比较不完全相同的方法似乎毫无意义。此外,对单个迭代进行基准测试似乎是可疑的,特别是当您甚至没有首先对方法进行JIT时。幸运的是,您提到了“但这些评估都会在睡觉前进行。”斯威普的答案在这里缺失的是,即使这个问题看起来像“时间和记忆差异”这样简单,实际上还有更多的问题。重要的是要记住,如果你不调用ToList
或任何其他“物化”"因此,对基础集合的任何更改都将反映在各个元素求值之间。因此,如果在睡眠之间向集合添加新项,则这些添加将反映出来……如果调用ToList,则情况并非如此。在这种情况下,无论原始集合发生了什么变化,这些更改都将反映出来不被反映。这可能是期望的,也可能是不期望的,这取决于具体情况,因此在某些情况下,调用ToList是可取的,而在其他情况下则不是。一个字等于一百个字,这肯定不仅仅是关于时间和内存。ToList的存在实际上改变了代码执行的顺序。