Jean-Paul S. Boodhoo

Sponsors

The Lounge

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Drop the temporary lists and leverage yield

I see lots of code bases throwing IList<T>, List<T> etc all over the place when half of the time all they really need is to return a set that can be either databound, or walked over and processed one at a time. For the scenarios where you don’t need to count the number of items or much of the other functionality that is exposed by the IList<T> interface, you can start to leverage the yield keyword more to tighten up the code.

Here is a common example, a service layer method call that returns a list of DTO’s that can be consumed by some sort of binding target. Assume that you are mapping domain objects into DTO’s to be consumed by the upper level layer (which could then be mapped into a presentation model). Let’s also assume that you have the following

  • Customer Domain Class
  • CustomerDTO Class
  • ICustomerDTOMapper interface (implementation knows how to map from domain to dto).
  • ICustomerRepository – repository interface to find customers
  • CustomerTask – service layer class

Here is the CustomerTask class with its appropriate dependencies injected, and the existing GetAllCustomers method using the temporary list:

 

public interface ICustomerDTOMapper { CustomerDTO MapFrom(Customer customer); } public interface ICustomerRepository { IEnumerable<Customer> All(); } public class CustomerTask { private ICustomerRepository customerRepository; private ICustomerDTOMapper customerDTOMapper; public CustomerTask(ICustomerRepository customerRepository,ICustomerDTOMapper customerDTOMapper) { this.customerRepository = customerRepository; this.customerDTOMapper = customerDTOMapper; } public IEnumerable<CustomerDTO> GetAllCustomers() { IList<Customer> results = new List<Customer>(); foreach (Customer customer in customerRepository.All()) { results.Add(customerDTOMapper.MapFrom(customer)); } return results; } }

With a  small change the code in the GetAllCustomers method can be changed to the  following:

public IEnumerable<CustomerDTO> GetAllCustomers() { foreach (Customer customer in customerRepository.All()) { yield return customerDTOMapper.MapFrom(customer); } }

This is a small change, but handy nontheless. Again, this is not new information, I just think that more people could start taking advantage of yielding in more situations where full blown lists are not called for.


Posted 10-10-2007 9:15 AM by bitwisejp
Filed under:

[Advertisement]

Comments

Sean wrote re: Drop the temporary lists and leverage yield
on 10-10-2007 12:15 PM

Good point, but in many cases though you are interested in having the list/collection to perform reduction or some sort of customization. Unless you know for sure you not going to need it you cannot count on yield i guess.

Jeremy D. Miller wrote re: Drop the temporary lists and leverage yield
on 10-10-2007 12:45 PM

I went down this route in StoryTeller and it led to some cleaner code.

Maybe as importantly, in high performance/large dataset stuff, this is a bit of a performance gain as well.

Adam Vandenberg wrote re: Drop the temporary lists and leverage yield
on 10-10-2007 12:47 PM

@Sean: You can still do "new List<Customer>(obj.GetAllCustomers())" if you really need a for-reals list.

sergiopereira wrote re: Drop the temporary lists and leverage yield
on 10-10-2007 12:54 PM

JP, besides the enlightening comments above, could you educate me a little bit here? Wouldn't it too rude for the service layer to limit what I can do with "my" data since there's always the chance you'll need, for example, a this[] indexer on the data sooner or later?

GadgetGadget.info - Gadgets on the web » Drop the temporary lists and leverage yield wrote GadgetGadget.info - Gadgets on the web &raquo; Drop the temporary lists and leverage yield
on 10-10-2007 1:15 PM

Pingback from  GadgetGadget.info - Gadgets on the web &raquo; Drop the temporary lists and leverage yield

cmyers wrote re: Drop the temporary lists and leverage yield
on 10-10-2007 2:32 PM

@seriopereira: You can always turn the IEnumerable<T> into a List<T> if you need that functionality.  It's preferable to use the lowest base type whenever possible in APIs so as to not presume how the API consumer will use the data.

List<Customer> customers = new List<Customer>( customerTask.GetAllCustomers() );

@JP:  Do you have any special tricks for the 'DTOMapper'?  It gets tedious writing dto.CreateDate = entity.CreateDate all the time. Do you use Reflection? TypeConverter? Cloning?

cmyers wrote re: Drop the temporary lists and leverage yield
on 10-10-2007 2:33 PM

Also, with the new Sequence stuff in C# 3.0, you could just use the IEnumerable<T> extension methods:

IList<Customer> customers = customerTask.GetAllCustomers().ToList()

Patrick Smacchia wrote re: Drop the temporary lists and leverage yield
on 10-10-2007 3:05 PM

I wrote 2 articles on the subject that also shows how C#2 anonymous method and yield stuff are related to functional programming:

www.theserverside.net/.../showarticle.tss

www.theserverside.net/.../showarticle.tss

Actually I was very curious about the odd underlying compiler work and I found out that everything was clear once you explain with functionnal programming vocabulary.

Christopher Steen wrote Link Listing - October 11, 2007
on 10-11-2007 10:25 PM

Link Listing - October 11, 2007

Christopher Steen wrote Link Listing - October 11, 2007
on 10-11-2007 10:28 PM

Regional SharePoint Users Conference October 27-28 [Via: Gary Blatt ] SOAP/TCP Transport for WCF [Via:...

Paul Kinlan wrote re: Drop the temporary lists and leverage yield
on 10-12-2007 5:04 AM

Hi,  I generally agree with you, but the problem is that consumers of your layers don't really know  your layers

use "yield" and can therefore cause confusion when it comes to understanding how the data access layer holds a connection to the external resource.

For instance your "customerRepository.All()" could also be implementing yield and if a consumer of your "GetAllCustomers" method slowly itterates across the results(for instance in response to user actions - which are always a lot slower than the machine ;) ) it means you are holding a database connection for a lot longer than you sould be because the yield isn't evaluated until the consumer of GetAllCustomers asks for the next item and thus, I belive it is much more efficient to quickly get all the data out of the data layer and hold it in a temporary list and then use the "yield" pattern to itterate accross the data later when you are not worried about developers misuing the itteration.

Anyways,  Good blog.

Paul Kinlan

Christopher Bennage wrote re: Drop the temporary lists and leverage yield
on 10-22-2007 8:55 PM

Great tip!

Eric Lee wrote re: Drop the temporary lists and leverage yield
on 01-10-2008 6:00 PM

One thing to watch out for is that producing an IEnumerable using the yield keyword subtly changes the expected behavior of the enumerator.  If the caller tries to enumerate over the same enumerator twice (by using it in two different foreach loops, for example) all of your Customer objects will be created twice (creating performance problems) and the Customer objects seen by the first foreach loop will not be the same objects seen by the second foreach loop (creating logic problems).

This is especially disconcerting if the caller uses the first foreach loop to modify all of the customer objects in some manner and then tries to do something with them in the second loop.  The modifications will mysteriously disappear and will induce hair-pulling until the light dawns.  (Not that I'd know this from personal experience, of course . . . ahem.)

Granted, there's nothing explicit in the IEnumerable contract that says it has to return the exact same objects over multiple iterations but since that's the way it has always behaved before the yield keyword was introduced, people will often expect that behavior.

It's too bad there's no way to disallow IEnumerable.Reset on enumerators you create using the yield keyword.