Recently I’ve been working on an Outlook 2007/2010 add-in using VSTO 3.0. The add-in deals with messages and contacts in which I need to be able to get a subset of a collection using filters. Unfortunately the exposed collections in VSTO projects do not give you the Linq extensions due to the collection not implementing IEnumerable<>. For example, getting a collection of items from  the Inbox folder

var c = Globals.ThisAddIn.Application.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox).Items;

‘Items’ is a collection of Item and Item is really a more detailed version of Object – it could represent anything. You get a collection of ALL items in the Inbox folder which can be mail, tasks, etc. The first problem is that due to the mixed types (unless you just want to work with the Item type) foreach is unusable because of type casting. You WILL get exceptions. Using a for loop is required. But a for loop is also difficult because ‘Items’ doesn’t support [] accessors based on integers. Your only choice is to get the enumerator and keep track of everything yourself. Lastly, you do not get Linq extensions such as Where(), Any(), Select().

Needless to say that development is less than desirable after being spoiled by Linq over the last few years. However, there is a way to get what you want by using Cast<>() which is an extension method of IEnumarable. The Cast<>() method will convert a non-generic IEnumerable sequence of some type T to IEnumerable.

But there are problems even with this. Since the ‘Items’ collection is in fact very generic (meaning it might as well be an object array) not every item will be correct and you will end up with exceptions or null objects which means you still need to check each item. A bigger problem is when dealing with very large collections such as 20k-70k items. Imagine filling a DataSet with that many rows from SQL. Your thread would have to wait until the operation is done before you can even start reading the data and then you have the extra memory overhead of creating the additional collection.

This is where Linqqer comes in. It’s an extremely simple collection wrapper for IEnumerable based collections. Since the collection of ‘Items’ already exists, there is no need to recreate it. We just want to enumerate over it. Linqqer accomplishes a few things

  • Instant access to collection. Think of a DataReader vs a DataSet.
  • Wraps IEnumerable collections as IEnumerable<> to expose Linq extensions.
  • Provides type safety by checking the item for proper type before it’s used.
  • Does not require additional memory because it wraps the already existing collection.

The only thing Linqqer doesn’t do (at the moment) is provide integer based indexer access via [] for use with for loops. Not a big issue right now. Let’s see the code.

public class Linqqer<TCollection, UType> : IEnumerable<UType>, IEnumerator<UType>
        where TCollection : IEnumerable
    {
        private TCollection _collection;
        private IEnumerator _enumerator;

        public Linqqer(TCollection collection)
        {
            _collection = collection;
            _enumerator = _collection.GetEnumerator();
        }

        #region IEnumerable<U> Members

        public IEnumerator<UType> GetEnumerator()
        {
            return this;
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this;
        }

        #endregion

        #region IEnumerator<U> Members

        public UType Current
        {
            get { return (UType)_enumerator.Current; }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {

        }

        #endregion

        #region IEnumerator Members

        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }

        public bool MoveNext()
        {
            bool res = _enumerator.MoveNext();

            while (res == true && !(_enumerator.Current is UType))
            {
                res = _enumerator.MoveNext();
            }

            return res;
        }

        public void Reset()
        {
            _enumerator.Reset();
        }

        #endregion
    }

Just add this to your project and you’re ready to use it.

var c = new Linqqer<Microsoft.Office.Interop.Outlook.Items, Microsoft.Office.Interop.Outlook.MailItem>(Globals.ThisAddIn.Application.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox).Items);

Advertisements