OOM.NET Part 2 – Outlook Item Leaks
Matt Stehle used to be on my team prior and had blogged on the usage of Outlook Object Model (OOM) under .NET. However, his blog is being closed and I am re-blogging his content here.
Outlook item leaks are the most common OOM with .NET issues that we see and I've debugged enough of them to compile this list of the four basic mistakes that contribute to item leaks. An "item leak" is most commonly seen as an item that won't refresh or can't be saved in the Outlook UI. A common example of this problem is that a user will complain that they receive a dialog stating that they can't save or update an appointment in Outlook when a particular AddIn is loaded. This is because the AddIn has not released the item reference and Outlook is reusing it. In order to fix the problem properly you need to look at your code for a few of the common mistakes listed below…
Outlook Processes Reuse Cached Item References
If an item is in memory in Outlook.EXE then Outlook will reuse it when another action requests it. For example, if a COM AddIn leaks an item reference and a user is interacting with that item through the UI they might not be able to save their changes all of a sudden. This is especially true if the item were to be modified in the MAPI store after it has been leaked. An easy test would be to modify the suspected leaked item using MFCMAPI while Outlook is running then open the item in the Outlook UI and try to modify it. You will get a dialog saying that the item cannot be saved if you have leaked the item.
Do Not Chain Child Object References
One of the advantages of pure .NET programming is that you don't have to worry about memory management because the garbage collector and the dispose pattern handle all of that. What we have to remember that OOM programming in .NET is not pure .NET programming ***it is COM programming in .NET*** because of that the rules of COM apply here. Every object referenced must be released. When you chain objects you lose track of a reference that you need to release.
See the example below...
// This leaks an Attachments reference ProcessAttachments(email.Attachments);
// This doesn't leak Outlook.Attachments oAttachs = email.Attachments; ProcessAttachments(oAttachs);
oAttachs = null;
Since the reference to the Attachments object is maintained it can be released properly.
Leaking a Child Item Leaks the Parent
If a child item that holds an internal reference to its parent – such as attachments or recipients - is leaked then the parent is in turn leaked. In the example above, chaining and leaking the child Attachments collection of the email will cause the email itself to leak. The parent object will never get cleaned up regardless of how many times GC.Collect or ReleaseCOMObject are called.
Be Careful with foreach Loops
The basic use of a foreach loop to iterate through items in a collection that have an internal reference to their parent – see above - will leak items. Either avoid foreach loops altogether by using for loops...
// This leaks, GC.Collect will not help
foreach (Outlook.Attachment oAttach in oAttachs)
oAttach = null;
// This doesn't leak
for (int i = 1; i < oAttachs.Count; i++)
for (int i = 1; i =< oAttachs.Count; i++)
Outlook.Attachment oAttach = oAttachs[i];
// Do nothing with attachment
oAttach = null;
…or follow the notes in this discussion to articulate the handling and cleanup of the underlying objects in the enumerator created by the foreach loop to ensure that things get cleaned up properly…
IEnumerator attachsEnum = oAttaches.GetEnumerator();
Attachment at = (Attachment)attachsEnum.Current;
ICustomAdapter adapter = (ICustomAdapter)attachsEnum;