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.