- 背景知识
- for 循环重复执行一个语句或语句块,直到指定的表达式计算为 false 值。for 循环对于迭代数组和顺序处理非常方便。
foreach 语句为数组或对象集合中的每个元素重复一个嵌入语句组。foreach 语句用于循环访问集合以获取所需信息,但不应用于更改集合内容以避免产生不可预知的副作用;
迭代器是 C# 2.0 中的新功能。迭代器是方法、get 访问器或运算符,它使您能够在类或结构中支持 foreach 迭代,而不必实现整个 IEnumerable 接口 具体参考:MSDN
- for VS foreach
- for和foreach都可以对集合和数组的元素进行逐个处理。 关于for和foreach有什么区别那?主要从以下方面进行比较
- 语法方面
- 语义方面
- 性能方面
- 线程安全
- 使用foreache的一点体会
网上上有很多关于他们的讨论, 阅读本文之前你可以先参看其他文章:
- 语法方面
-
- 简洁,方面
for
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
for
List<Car> carlist = new List<Car>();
for (int i = 0; i < carlist.Count; i++)
{
Car c = carlist[i] as Car;
Console.Write("{0}", c.Weight);
} foreach
List<Car> carlist = new List<Car>();
foreach (Car c in carlist)
{
Console.Write("{0}", c.Weight);
}
可以看出,对于集合的遍历, foreach比for简洁。当然,如果只遍历集合的一部分,foreach爱莫能助了。
- 迭代变量的读写性方面 对于for,迭代变量是可读可写的,foreach是只读的。为什么?
能使用foreach的集合必须实现IEnumerable接口。Reflector IEnumerable的IL代码如下:
[ComVisible(true), Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
在接着看 System.Collections.IEnumerator GetEnumerator() 里面的:
[ComVisible(true), Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
注意:
object Current { get; }
是不是很熟悉,只读属性,就解释了为什么foreach迭代变量为只读的了。
- 语义方面
- 学习开发语言,我觉得语义比语法更重要.学习英语我觉得也是如此。
foreach 传达出来的语义就是对于集合一个一个检索,而for就是一循环结构。
个人觉得foreach 可以嗅出面向对象的味道来。
- 性能方面
- 关于性能方面我不能多说什么,你可以自己动手测试。下面是我的一段测试代码,供参考,如果你有更好的性能代码测试,可以写出来.
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
性能测试比较代码
class Program
{
static List<int> list1 = new List<int>();
static List<int> list2 = new List<int>();
static TimeSpan TimeSpan1 = new TimeSpan();
static TimeSpan TimeSpan2 = new TimeSpan();
static void Main(string[] args)
{
for (int i = 0; i < 2000000; i++)
{
list1.Add(i);
list2.Add(i);
}
for (int i = 0; i < 10; i++)
{
ThreadStart s1 = new ThreadStart(TestFor);
Thread t1 = new Thread(s1);
t1.Start();
ThreadStart s2 = new ThreadStart(TestForeach);
Thread t2 = new Thread(s2);
t2.Start();
}
for (int i = 0; i < 10; i++)
{
ThreadStart s2 = new ThreadStart(TestForeach);
Thread t2 = new Thread(s2);
t2.Start();
ThreadStart s1 = new ThreadStart(TestFor);
Thread t1 = new Thread(s1);
t1.Start();
}
Console.WriteLine("正常for:" + TimeSpan1.Seconds.ToString() + ":" + TimeSpan1.Milliseconds.ToString());
Console.WriteLine("foreache:" + TimeSpan2.Seconds.ToString() + ":" + TimeSpan2.Milliseconds.ToString());
Console.Read();
}
static void TestFor()
{
TimeSpan start = new TimeSpan(DateTime.Now.Ticks); //获取当前时间的刻度数
for (int i = 0; i < list1.Count; i++)
{
list1[i].ToString();
}
TimeSpan end = new TimeSpan(DateTime.Now.Ticks);
TimeSpan sub = start.Subtract(end).Duration(); //时间差的绝对值
TimeSpan1 += sub;
}
static void TestForeach()
{
TimeSpan start = new TimeSpan(DateTime.Now.Ticks); //获取当前时间的刻度数
foreach (int c in list2)
{
c.ToString();
}
TimeSpan end = new TimeSpan(DateTime.Now.Ticks);
TimeSpan sub = start.Subtract(end).Duration(); //时间差的绝对值
TimeSpan2 += sub;
}
} 究竟如何选择,参见:《Effective C#》 - 条款11:优先采用foreach循环语句
C#的foreach语句不仅仅只是do... while或者for循环语句的一个变体。它会为我们的集合产生最好的遍历代码。实际上,foreach语句的定义和.NET框架中的集合接口密切相关。 对于一些特殊的集合类型,C#编译器会产生具有最佳效率的代码。遍历集合时,我们应该使用foreach语句,而非其他的循环构造
- 线程安全
- 对于一个集合,在遍历过程中,如果集合发生异动,foreach会发生异常,而for不会。
这个是一个曾经电话面试过我的一个工程师说的,我觉得这句话值得商榷,只是在.Net Framework提供的常见集合(Array,List等)会发生,发生异常代码参考:
-
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
class Program
{
static List<int> mList = new List<int>();
static void Main(string[] args)
{
for (int i=0; i < 100000;i++)
{
mList.Add(i);
}
ThreadStart s1 = new ThreadStart(Remove);
Thread t1 = new Thread(s1);
t1.Start();
foreach (int c in mList)
{
Console.WriteLine(c.ToString());
if (c.ToString() == "1003")
break;
}
Console.Read();
}
static void Remove()
{
mList.RemoveAt(1000);
}
} 这个异常原因可以参见: 在Dictionary使用foreach的注意 如果你自己动手实现迭代器就不会了。代码如下:
-
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
class Program
{
//static List<int> mList = new List<int>();
static TestEnum mList = new TestEnum(1000000);
static void Main(string[] args)
{
for (int i=0; i < 1000000;i++)
{
mList[i]=i;
}
ThreadStart s1 = new ThreadStart(Remove);
Thread t1 = new Thread(s1);
t1.Start();
foreach (int c in mList)
{
Console.WriteLine(c.ToString());
if (c.ToString() == "1003")
break;
}
Console.Read();
}
static void Remove()
{
mList.RemoveAt(1000);
}
}
//---------------------------------------------------------------
public class TestEnum : IEnumerable
{
//定义一个point结构的数组
private int[] ints;
public int Length
{
get
{
return ints.Count();
}
}
//类的构造函数,用于初始化point结构数组
public TestEnum(int numofpoint)
{
this.index = -1;
ints = new int[numofpoint];
for (int i = 0; i < ints.Length; i++)
{
ints[i] = i;
}
}
//实现IEnumerable接口的GetEnumerator方法,返回一个IEnumerator,这里返回我们的自定义类,因为要对这个类的对象进行迭代
public IEnumerator GetEnumerator()
{
for (int i = 0; i < ints.Length; i++)
{
yield return ints[i];
}
}
public void RemoveAt(int index)
{
int[] temInts = ints.Clone() as int[];
ints = new int[ints.Length - 1];
for (int i = 0; i < index; i++)
{
ints[i] = temInts[i];
}
for (int i = index; i < temInts.Count(); i++)
{
ints[i-1] = temInts[i];
}
int t = 0;
}
//定义类索引
public int this[int index]
{
get
{
return ints[index];
}
set
{
ints[index] = value;
}
}
} 对于一个集合,在遍历过程中,如果集合发生异动,foreach会发生异常,而for不会,foreach本身没有错,关键看你如何实现迭代器。
- 使用foreache的一点体会
-
- 使用lock 对于你能掌控的代码,把这个集合变量作为一个临界变量,使用lock加锁,确保代码不会出现“集合已修改;可能无法执行枚举操作。”类似异常。如下代码供参考:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
class Program
{
static List<int> mList = new List<int>();
static void Main(string[] args)
{
for (int i=0; i < 1000000;i++)
{
mList.Add(i);
}
ThreadStart s1 = new ThreadStart(Remove);
Thread t1 = new Thread(s1);
t1.Start();
lock (mList)
{
foreach (int c in mList)
{
Console.WriteLine(c.ToString());
if (c.ToString() == "1003")
break;
}
}
Console.Read();
}
static void Remove()
{
lock (mList)
{
mList.RemoveAt(1000);
}
}
} 注意lock用法。
- 使用本地变量拷贝 把这个集合变量拷贝到本地变量,然后对本地变量进行遍历。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
List<int> temList = mList.ToList();
foreach (int c in temList)
{
Console.WriteLine(c.ToString());
if (c.ToString() == "1003")
break;
}
-