Don't Fall into the IEnumerable<T> Trap
2 min read
2 min read
Recently I upgraded some code of our company-internal class library and observed a plausible but still tricky problem. See yourself.
public IEnumerable<SomeObject> GetAllValidations()Then, inside another method in some other class there was the following code:
{
foreach...
yield return new ValidationObject(...);
}
public void HandleValidationErrors(IEnumerable<ValidationObject> validationObjects)Now guess what, line 24 gave me an exception when I wanted to access the dictionary with the validationObj as key, telling me that the key was not present. I debugged the code and the obj was present!? My first thought: did someone override the
{
IDictionary<ValidationObject, IList<ValidationErrors>> cache = new Dictionary<ValidationObject, IList<ValidationErrors>>();
foreach(var validationObj in validationObjects)
{
cache.Add(validationObj, validationObj.ValidationErrors.ToList());
}
ActivateValidatorsAccordingToValidationErrors(validationObjects, cache);
//snip snip: If cache not empty, some validation objects haven't been treated/found
}
public void ActivateValidatorsAccordingToValidationErrors(IEnumerable<ValidationObject> validationObjects, IDictionary<ValidationObject, IList<ValidationErrors>> cache)
{
foreach(var valObj in validationObjects)
{
var validator = GetValidatorByValidationObj(valObj);
if(validator != null)
{
//activate validator
//remove ValidationObj from cache
}
}
}
Equals()
??...but that wasn't the case.IEnumerable<T>
. Did you hear about the "lazy-evaluation"
of IEnumerables? I did, fortunately. The problem here was that when the code entered the HandleValidationErrors(...)
method, passing in the IEnumerable, that latter has not been evaluated so far. And then I had the situation of a possible
multiple enumeration (Resharper tells you that, so don't ignore it!). So what does that mean? When the IEnumerable
was accessed 1st in line 5, the method GetAllValidations()
was called, actually executing
the enumeration with new instances of type ValidationObject. So far so good. Then when the loop in
line 18 was executed, the same happened, returning another, new
instance of a ValidationObject, basically another IEnumerable<ValidationObject>
than I previously
added to the cache
variable. And here we are, now the exception when accessing the dictionary is quite
obvious, isn't it?var theList = theValidationObjectEnumeration.ToList<ValidationObject>()which "materializes" the list and prevents the multiple enumeration problem.
IEnumerable<T>
and
if the method returning the IEnumerable was written by some other dev. So watch out!