Thursday, October 14, 2010

Be careful with Linq within Exception Handling Block

Exception handling try {} catch {} finally {} in .NET always guarantee the finally clause will be executed. However, deferred execution of Linq prove to be able to make a difference, once again thanks to the side effects of linq like I've discussed in the past.

Consider the code block below: -
        protected override IEnumerable<byte[]>  DoReadAsRaw()
        {
         try
         {
          return _settings
           .FieldInfos
           .Select(item => _innerReader.ReadBytes(_innerReader.ReadInt32()));
         }
         catch (EndOfStreamException)
         {
          return null;
         }
        }
This is to read out an Enumerable of byte array. I have another code block below that will call into this method and utilizing the Enumerable of byte array as such: -
         var rawData = DoReadAsRaw();

         return rawData == null ? null : new Record<byte[]>(_settings.FieldInfos, rawData);
Within the Record Constructor, I have the codeblock to iterate thru the enumerable byte array as such: -
        public Record(IEnumerable<FieldInfo> fieldInfos, IEnumerable<T> values)
        {
            if (fieldInfos == null)
            {
                throw new ArgumentNullException("fieldInfos");
            }

            if (values == null)
            {
                throw new ArgumentNullException("values");
            }

            var fieldInfoArray = fieldInfos.ToArray();
            var valueArray = values.ToArray();

            // checks the length of field and data to ensure their length is matching.
            if (fieldInfoArray.Length != valueArray.Length)
            {
                var message = string.Format("Fields and values count mistmatched. Expected {0} fields but was {1}.",
                                            fieldInfoArray.Length, valueArray.Length);
                throw new IndexOutOfRangeException(message);
            }

            _fields = new T[fieldInfoArray.Length];
            _itemNameIndexMap = new Dictionary<string, int>();

            for (var i = 0; i < fieldInfoArray.Length; i++)
            {
                var currentFieldInfo = fieldInfoArray[i];

                _fields[i] = valueArray[i];
                _itemNameIndexMap.Add(currentFieldInfo.Name, i);

                if (!currentFieldInfo.IsKeyField) continue;

                IdentifierFieldIndex = i;
                IdentifierFieldName = currentFieldInfo.Name;
            }
        }
Guess what when unit testing?
EndOfStreamException was not handled!

the past experience of Linq's side effects helps me to quickly identifying that the culprit was the absent of ToArray() or ToList() call right after the linq statement within the try-catch block.

Soon as ToArray() is added into the code, it works beautifully and passed the unit test.

So, best practise for Linq Statement within the try catch block I would recommend is: -

If you are returning anything to the caller after the try catch block, ensure you always force create real collection by calling to ToArray() or ToList().

Thank you.