Using IEnumerable Is Better Than Returning Lists
Or so my experience lately has led me to believe. I rewrote an older application I had written (and parts of which you have seen before) to work with WPF. Part of the rewrite also involved coding to interfaces, rather than class constructs. I also had a nice opportunity to try out one of the more interesting facets of enumerations: return yield.
First, let's look at how I used to do this:
1: /// <summary>
2: /// Add a new Tutor node to the tutor file.
3: /// </summary>
4: /// <param name="tutor">Dictionary object containing relevant tutor data</param>
5: /// <returns>true if node added, false if node was not added</returns>
6: public bool AddTutor( Dictionary<string, string> tutor )
7: {
8: if ( tutor["first"] != String.Empty && tutor["last"] != String.Empty && tutor["email"] != String.Empty )
9: {
10: try
11: {
12: XDocument xd = XDocument.Load( String.Format( @"{0}tutors.xml", PATH ) );
13: var lastnode = xd.Root.Elements( "tutor" ).LastOrDefault();
14: int temp;
15: if ( !lastnode.Equals( null ) && int.TryParse( lastnode.Attribute( "id" ).Value, out temp ) )
16: {
17: xd.Root.Add(
18: new XElement( "tutor",
19: new XAttribute( "id", temp + 1 ),
20: new XAttribute( "type", tutor["type"] ),
21: new XAttribute( "active", "true" ),
22: new XElement( "first", tutor["first"] ),
23: new XElement( "last", tutor["last"] ),
24: new XElement( "email", tutor["email"] ),
25: new XElement( "nick", tutor["nick"] )
26: )
27: );
28:
29: xd.Save( String.Format( @"{0}tutors.xml", PATH ) );
30:
31: return true;
32:
33: }
34: return false;
35: }
36: catch ( Exception ex )
37: {
38: throw (
39: new Exception(
40: String.Format( "An error occurred. The tutor could not be saved. Details of this error:nn{0}", ex.Message )
41: )
42: );
43: }
44: }
45: else
46: {
47: throw ( new Exception( "Cannot add a null tutor to the datasource" ) );
48: }
49: }
It works, but it always felt pretty clunky, something that seemed to be proven out by less-than-stellar performance. When I had a chance to recode it, I made a conscious effort to think in terms of interfaces, and how to code to them.
After a few fits and starts, I wound up with this snippet in place of the earlier snippet:
1: /// <summary>
2: /// Returns an enumerated list of IMailable:MailablePerson objects, sans persons of the type specified by the <code>filter</code> parameter
3: /// </summary>
4: /// <remarks>The filter can be passed in as an empty string, if so desired, but cannot be a null object</remarks>
5: /// <param name="file">Path to the XML data file</param>
6: /// <param name="filter">A person type (role) to exclude from the list</param>
7: /// <returns>An enumerated list of IMailable:MailablePerson objects</returns>
8: internal IEnumerable<IMailable> GetMailables( string file, string filter )
9: {
10: file = String.Format( @"{0}{1}.xml", Properties.Settings.Default.PATH, file );
11: XDocument xd = XDocument.Load( file );
12:
13: // Method syntax (lambda expression)
14: var tuts = xd.Root
15: .Elements( DATA_NODE )
16: .Where( x => x.Attribute( "type" ).Value != filter );
17:
18: foreach ( var t in tuts )
19: {
20: yield return new MailablePerson(
21: t.Attribute( "id" ).Value,
22: t.Element( "first" ).Value,
23: t.Element( "last" ).Value,
24: t.Element( "email" ).Value,
25: t.Attribute( "type" ).Value,
26: false
27: );
28: }
29: // This should cover the edge case where no tutors match the filter value;
30: yield break;
31: }
Now, I don't know about you, but I will take the extra 18 lines I save in the new method any day. The new method also has the advantage of being able to be used for any XML file the matches my XML tree and will populate any object that implements my interface.
The most useful benefit of the new method comes from how and when the XML file is parsed. In the original method, extraction happens when I call the method, and I'm being given a rather bloated class type in List<> (I could have derived benefit from simply changing the return type to IList<>, but that's another discussion). By using IEnumerable, this method is actually returning a reference to what I want to call a "primed" datasource. The actual extraction occurs when something accesses the IEnumerable object returned by the method. In my case, I am loading a WPF ComboBox, so the extraction is actually postponed a bit until the constructor is fired.
Using the IEnumerable interface also lets me use the Iterator pattern. Each time through the foreach loop, I am returning one object to the calling object, and behind the scenes the runtime is calling the MoveNext() method supplied by the IEnumerable iterface. The end result seems to be a much snappier load in my application.
I'll add more stuff later, but for now, I hope you've maybe started to see why I like this "new" (to me anyway) method of coding...
