Deferred Evaluation of Collections Passed as Parameters

May 26th 2014 Validation LINQ

Most of us know that having a parameter of type List is not recommended and that IList or even IEnumerable should be used instead when a parameter is only going to be used for iterating through the list of items. We usually only take advantage of this by passing in different types of collections, such as arrays, lists, etc., although IEnumerable can prove much more useful in certain scenarios.

Let's take a look at one such scenario - a unique validator, ensuring that a certain property remains unique in the collection. Its interface could look like this:

public interface IUniqueValidator<T>
{
  IEnumerable<T> Items { get; set; }
  bool IsValid(T item);
}

The actual implementation is not really important. It only matters that there is a list of all items available to the validator, against which the validated item's unique property can be compared. It doesn't really matter, what kind of a collection we are using outside the validator (in our view model, for example); as long as it implements IEnumerable, we can pass it to the validator.

Still, this only works, if the collection outside the validator contains items of the same type, required by the validator. This might seem obvious, but often this isn't the case. Even in our above described scenario, it can easily happen that the view model will contain an instance of ObservableCollection<Wrapper<T>>, e.g. to provide additional properties for commands and UI specific flags.

Such a collection can't be directly passed to the validator; at least not without changing the validator to be aware of the wrapper and be able to obtain the original type from it. Of course, one could always define casting operators for this conversion, but IEnumerable allows a much more elegant solution to this problem.

While all collection types implement IEnumerable, this is not the only way to implement it. A very good example of that is IQueryable - the core of LINQ, returned by most of its extension methods. Without knowing it, you are depending on it every time you use LINQ. The most important feature of this interface (apart from it actually implementing IEnumerable) is deferred execution. The filtering and projections are performed every time the collection is enumerated.

In our scenario this allows us to pass "live" projections of our wrapped items to the validator. Having the following definitions:

public class UniqueValidator<T> : IUniqueValidator<T>
{
  public IEnumerable<T> Items { get; set; }
  public bool IsValid(T item)
  {
    // perform unique validation against Items
  }
}

public class Wrapper<T>
{
  public T Item { get; set; }
}

public class ItemType
{ }

public ObservableCollection<Wrapper<ItemType>> ItemCollection { get; set; }

One can easily write the following code:

void Main()
{
  ItemCollection = new ObservableCollection<Wrapper<ItemType>>();

  var validator = new UniqueValidator<ItemType>
  {
    // notice the lack of ToList() or ToArray at the end
    Items = ItemCollection.Select(wrapper => wrapper.Item)
  };
}

This doesn't create a static collection with the projected contents of ItemCollection at the time this code executes. Items will contain exactly the items from the ItemCollection, no matter how much we change it, because the Select() projection will only be performed when Items property is actually enumerated.

We can take this even a step further. IQueryable does not implement filtering and projections only on local collections (the so called LINQ to objects), but it can also work against databases (LINQ to SQL, Entity Framework and other ORM implementations using LINQ syntax). In this case the process of enumeration can actually query the database and read the results off the connection. Of course, one needs to be aware of this when using such a validator, but it can still offer a lot of additional flexibility.

Copyright
Creative Commons License