Working on toepoke today and I wanted to loop through a list of objects and sync up some properties, so I started off by looping through a normal foreach construct.
I vaugely remember there was a ForEach method hanging off the intellisense for list objects, so a quick google revealed … not much (granted I didn’t do a extensive search … I’m very much in the ”Not on the first page … I’ll figure it out myself!” camp
). Anyway seems like a good opportunity for a blog.
So what example to use as an illustration? What about a noddy Person object with the following properties:
- Name – useful for seeing what’s going on
- DOB – which we’ll generate when create a list of Person
- Age – which is the property we’re going to loop around and calculate.
Which gives us the following noddy class:
public class Person
{
public string Name { get; set; }
public DateTime DOB { get; set; }
public int Age { get; set; }
}
We’re going to drive this example through a unit test (see full excert below), but I’ll just concerntrate on the relevant bits here. So next up let’s populate a list of people to create our test data.
List<Person> people = new List<Person>() {
new Person() { Name = "Fred Flintstone", DOB = DateTime.Now.AddYears(-65) },
new Person() { Name = "Homer Simpson", DOB = DateTime.Now.AddYears(-40) }
};
Simple enough, couple of cases, one aged 65, the other aged 40. Our simple ForEach will loop through and work out the age of each instance. To do this we’ll just take the DOB away from the current time to delivery the number of years.
The ForEach method is simply a delegate (which in essence is just a function) which may or may not take any arguments. Ours isn’t taking any arguments (it’s a simple example after all
). The code looks like this:
people.ForEach(
person => { person.Age = (DateTime.Now - person.DOB).TotalYears(); }
);
Now we’re working against a list of type Person which is the person => bit. The right hand side inside the curly brackets is in basically a method created on the fly for the purposes of the ForEach method. So in essence the above is saying “for each person in the list of person set the age to the total number of years between the person’s date of birth and now”. Doesn’t look to scary now does it
?
The more observant of you will have noticed that TotalYears doesn’t exist in a TimeSpan object. And you’d be absolutely correct; sadly the TimeSpan object doesn’t provide a TotalYears method because it’s quite complex to work out due to leap years and … erm … other stuff that’s hard to work out
. So to help out with our example, it’s time for a little extension method on the TimeSpan class:
public static class TimeSpan_Extensions
{
public static int TotalYears(this TimeSpan ts)
{
return (int)ts.TotalDays / 365;
}
}
I would recommend you don’t use that method for any production code whatsoever, I will guarantee bugs! But it’s perfect for our purposes.
Hopefully you’ve found this useful, and the full source is posted below, good luck and good searching.
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Blog.Tests
{
public static class TimeSpan_Extensions
{
public static int TotalYears(this TimeSpan ts)
{
return (int)ts.TotalDays / 365;
}
}
[TestClass]
public class ForEach
{
public class Person
{
public string Name { get; set; }
public DateTime DOB { get; set; }
public int Age { get; set; }
}
[TestMethod]
public void Can_Calculate_Age_Using_ForEach()
{
List<Person> people = new List<Person>() {
new Person() { Name = "Fred Flintstone", DOB = DateTime.Now.AddYears(-65) },
new Person() { Name = "Homer Simpson", DOB = DateTime.Now.AddYears(-40) }
};
// Yup we could use ForEach here too, but I don't want to confuse what we know have to use (foreach) with what we don't (ForEach)!
foreach (Person p in people) {
Assert.AreEqual(0, p.Age);
}
// loop through each person and set the Age property to their calculated age
people.ForEach(
person => { person.Age = (DateTime.Now - person.DOB).TotalYears(); }
);
// element 0 is Fred Flintstone
Assert.AreEqual(people[0].Age, 65);
// element 1 is Homer Simpson
Assert.AreEqual(people[1].Age, 40);
// wooo, another ForEach sample, just spitting out the results
people.ForEach(
a => {
TestContext.WriteLine(
string.Format("Person ({0}, {1}, {2})", a.Name, a.DOB.ToShortDateString(), a.Age)
);
}
);
}
private TestContext testContextInstance;
/// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}
}
}</pre>
</span></span></span></span><span style="color: #0000ff;"><span style="color: #000000;"><span style="color: #0000ff;"><span style="color: #000000;">