Iterating through SPList.Items using a for (int i) loop is bad for performance, as each call to SPList.Items causes a trip to the DB and will retrieve all items. See http://blog.dynatrace.com/2009/01/11/the-wrong-way-to-iterate-through-sharepoint-splist-items/.
I have always wondered whether using a foreach loop would cause the same issue.
I decided to use TypeMock to find out. My plan is to create a fake list, then setup the fakeList.Items property so that when called, it will invoke a substitute method instead. This substitute method will increment a count each time it is invoked, and return the fakeList.Items property.
Here is my setup code.
int itemsCallCount = 0; SPListItemCollection itemCollection; [TestMethod] public void TestMethod1() { //Create a fake list var fakeList = Isolate.Fake.Instance<SPList>(); //Setup the fakeList.Items so that when fakeList.Items.GetEnumerator() is called, 3 items will be returned. //This is because GetEnumerator() is what will be called in the foreach loop. SPListItem[] items = new SPListItem[3]; IsolateFaker.FakeEnumeratorElements<SPListItem>(fakeList.Items, items); //Setup the fakeList.Items.Count also. This is what will be used in the for (int i) loop. Isolate.WhenCalled(() => fakeList.Items.Count).WillReturn(3); //Save the setup of fakeList.Items. This is what we will return in our substitue tracking method. itemCollection = fakeList.Items; //Hook in our substitue method so we can track the calls to fakeList.Items. Isolate.WhenCalled(() => fakeList.Items).DoInstead((context) => ReturnItemCollectionAndTrackCount(context)); } private SPListItemCollection ReturnItemCollectionAndTrackCount(MethodCallContext context) { itemsCallCount++; return itemCollection; }
IsolateFaker.FakeEnumeratorElements() is a helper method I wrote that is not included here for brievity.
Here is my test target:
public int IterateUsingForEach(SPList list) { int count = 0; foreach (SPListItem item in list.Items) { var temp = item; count++; } return count; } public int IterateUsingForInt(SPList list) { int count = 0; for (int i = 0; i < list.Items.Count; i++) { var temp = list.Items[i]; count++; } return count; }
Here is my actual unit test method:
//Test using foreach. This will pass. Target target = new Target(); Assert.AreEqual(3, target.IterateUsingForEach(fakeList)); Assert.AreEqual(1, itemsCallCount); //Test using for (int i). This will fail. itemsCallCount = 0; Target target2 = new Target(); Assert.AreEqual(3, target2.IterateUsingForInt(fakeList)); Assert.AreEqual(1, itemsCallCount);
So what’s the conclusion? Foreach invokes the Items property once. For (int i) invokes the Items property 7 times. This means using Foreach does not cause the performance issue as for (int i) would.
Heck, if you know the CLR/MSIL well enough you probably already know the answer – but nevertheless it was interesting to play around with the Typemock stuff :D.