Mark Needham

Thoughts on Software Development

Coding: Missing abstractions and LINQ

with 8 comments

Something which I’ve noticed quite a lot on the projects that I’ve worked on since C# 3.0 was released is that lists seem to be passed around code much more and have LINQ style filters and transformations performed on them while failing to describe the underlying abstraction explcitly in the code.

As a result of this we quite frequently we end up with this code being in multiple places and since it’s usually not very much code the repetition goes unnoticed more than other types of duplication might do.

A typical example of this might be the following:

public class SomeFooHolder
{
	public List<Foo> Foos { get; set }
}

An example of how this might be used is like so:

var someFooHolder = new FooHolder(...);
someFooHolder.Foos.Select(f => f.Completed);

That code would typically be repeated in other places in the code where we want to get all the completed foos.

Although it’s a simple change, as a first step I prefer to make that concept more explicit by putting ‘CompletedFoos’ on ‘SomeFooHolder’:

public class SomeFooHolder
{
	public List<Foo> Foos { get; set; }
	public List<Foo> CompletedFoos 
	{ 
		get { return Foos.Select(f => f.Completed); }
	}
}

Perhaps an even better solution would be to create an object ‘Foos’ to encapsulate that logic further:

public class Foos
{
	private readonly List<Foo> foos;
 
	public Foos(List<Foo> foos)
	{
		this.foos = new List<Foo>(foos.AsReadOnly());
	}
 
	public Foos Completed
	{
		get { return new Foos(foos.Select(f => f.Completed)); }
	}
}

As I’ve written about previously I prefer to wrap the list rather than extend it as the API of ‘Foos’ is more expressive since we don’t have all the list operations available to any potential users of the class.

Written by Mark Needham

January 17th, 2010 at 7:09 pm

Posted in Coding

Tagged with ,

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #519

  • http://www.jacopretorius.net Jaco Pretorius

    Interesting post – I agree that we tend to duplicate LINQ expressions quite often. If I find I’m re-using a LINQ expression I usually write a custom filter (extension method) and then simply re-use the extension method. The cool part about this approach is that you can unit test the filter and your code also becomes much more readable.

    For example, you might write

    users.WithId(5) instead of users.Single(u => u.Id == 5)

  • http://blog.jordanterrell.com Jordan Terrell

    I agree with Jaco. When the LINQ expression is fairly simple and used frequently, then writing an extension method makes good sense. However, when the expression is complicated or composed of multiple, perhaps reusable parts, I like the Specification pattern: http://martinfowler.com/apsupp/spec.pdf

    For example:

    someFooHolder.Foos.Meets();

    or

    someFooHolder.Foos.Meets(new CompletedFooSpecification());

    That may seem more involved – and it is. However, the specification pattern can give you (more) Separation of Concerns and composability, if you *need* it. If not, then extension methods is an good choice.

  • http://blog.jordanterrell.com Jordan Terrell

    Looks like the commenting engine striped out some of my example. This first example should be:

    someFooHolder.Foos.Meets ();

    Hope this doesn’t get striped out also!

  • http://nomadic-developer.com/ Aaron Erickson

    Maybe there is a good opportunity for Dynamic here :)

    Something that converts the method name into the appropriate lambda dynamically… too magical?

  • http://blog.jordanterrell.com Jordan Terrell

    And it did! DOH! One more time, with HTML escape sequences ;-)

    someFooHolder.Foos.Meets<CompletedFooSpecification>();

  • http://www.markhneedham.com Mark Needham

    @Aaron yeh that would work quite well in this situation as you say. How would this type of thing work in Ruby?

  • http://nomadic-developer.com/ Aaron Erickson

    No idea – I don’t write Ruby often :/

    But inheriting your base from DynamicObject I can easily imagine how this will work.