Sunday, July 27, 2008

More fun with 'yield return'

I'm a big fan of custom iterators that were introduced with C# 2.0. I keep on finding new uses for them. Today I was thinking about rendering contact addresses in Suteki Shop. I have a contact that looks like this:

sutekishopContact

Only Firstname, Lastname, Address1 and CountryId are required fields, all the others are optional. Previously I was rendering a contact like this:

sutekishopContactOldRender

If say, Address1, Address2 and Town were missing the contact renders with gaps in it. Also it's a bore writing out each line like that. With 'yield return' I was able to add a custom iterator 'GetAddressLines' to my contact class so that I can simply render out the lines that are available:

sutekishopContactRender

Here's the code:

public IEnumerable<string> GetAddressLines()
{
 yield return Fullname;
 yield return Address1;

 if (!string.IsNullOrEmpty(Address2))
 {
     yield return Address2;
 }

 if (!string.IsNullOrEmpty(Address3))
 {
     yield return Address3;
 }

 if (!string.IsNullOrEmpty(Town))
 {
     yield return Town;
 }

 if (!string.IsNullOrEmpty(County))
 {
     yield return County;
 }

 if (!string.IsNullOrEmpty(Postcode))
 {
     yield return Postcode;
 }

 yield return Country.Name;
}

8 comments:

jake scott said...

Haha cool idea cheers.

Also, why don't you use an extension method on string so that you can go

if (!Address2.IsNullOrEmpty){}

or

if (Address2.NotNullOrEmpty){}

DylanBeattie said...

yield return is truly beautiful, isn't it?

Jake - extension methods won't necessarily work here, because if Address2 is null, then Address2.IsNullOrEmpty will raise a NullReferenceException. You could still say (if (! Address2 == null || Address2.IsEmpty)), but String.IsNullOrEmpty is pretty well-established.

Stefan Lieser said...

@DylanBeattie:
The compiler translates a call to s.IsNullOrEmpty() into a call where s is passed as a parameter. So there is absolutely no problem with this extension method if the string variable contains null.

Mike Hadlow said...

Jake - nice. It seems like there are extension method replacements for amost every part of the BCL. I was looking at umbrella the other day, yet another extension library. It's not particularly easy to get one's head around, but it's an indication of what's possible with 3.5 syntax. A bit like jQuery for C# :)

Dylan - owch :P

Ken Egozi said...

@Mike:

yield return is cool, and this is certainly an interesting usage, that can serve as a helper for grokking 'yield', however, in this very scenario I'm not sure I'd take the same path.

As the order of fields is a UI concern, letting the entity decide about it is not something I like.

So you'd think about creating a special UI level method to build the list.

But then what will happen is simply a shift of the UI code to somewhere else.
We won't be gaining much, as we didn't eliminate duplication, but we will lose clarity - it won't be as clear what markup is being generated.

Even if the same piece of rendering recur through the application, I'd simply use a partial view for that.

Mike Hadlow said...

Ken, that's a good point. A partial view might be reasonable solution. I was also thinking of creating a generic solution where you provide an Action<Contact> delegate which describes the field order. Hmm, could be overkill.

jakescott said...

If one was to create an extension method on the Contact class, but that extension method class lived in the UI project of the solution - would this still be considered a violation of concerns?

Keen to here ideas on this, maybe the code gets cryptic if the same class is extended in multiple different places in a solution.

Mike Hadlow said...

Jake, If you were going to move it out of the Contact class and into some UI toolkit it would be nice to generalise it.