Running out of memory looping through mail items - c#

Hi I have a Outlook com addin that is doing some simple searching tricks for me. I am part way through putting it together but I am having issues with it running out of memory. The process is very simple and basically loops through an outlook folder checking each mailItem for a match. given the loop reinitialize the variables each time I would have expected the garbage collector to keep up but when I watch the memory it loses ~10m/sec until the system is out of memory and I get unhandled exceptions.
This is part of the code
private void FindInFolder(Outlook.MAPIFolder FolderToSearch)
{
Outlook.MailItem mailItem;
Outlook.MAPIFolder ParentFolder;
int counter = 0;
StatusBar.Text = "Searching in Folder " + FolderToSearch.FolderPath + "/" + FolderToSearch.Name;
StatusBar.Update();
this.Update();
foreach (COMObject item in FolderToSearch.Items)
{
counter++;
if (counter % 100 == 0)
{
StatusBar.Text = FolderToSearch.FolderPath + "/" + FolderToSearch.Name + " item " + counter + " of " + FolderToSearch.Items.Count;
StatusBar.Update();
if (counter % 1000 == 0)
{
GC.Collect();
}
}
if (item is Outlook.MailItem)
{
mailItem = item as Outlook.MailItem;
if (IsMatch(mailItem))
{
if (mailItem.Parent is Outlook.MAPIFolder)
{
ParentFolder = mailItem.Parent as Outlook.MAPIFolder;
ResultGrd.Rows.Add(mailItem.EntryID, ParentFolder.FolderPath, mailItem.SenderName, mailItem.Subject, mailItem.SentOn);
}
}
}
mailItem = null;
}
}
Which calls
private Boolean IsMatch(Outlook.MailItem inItem)
{
Boolean subBool = false;
Boolean NameBool = false;
try
{
if (null != inItem)
{
if (SubjectTxt.Text != "")
{
if (inItem.Subject.Contains(SubjectTxt.Text))
{
subBool = true;
}
}
else
{
subBool = true;
}
if (NameTxt.Text != "")
{
if (inItem.Sender != null)
{
if (inItem.Sender.Name.Contains(NameTxt.Text))
{
NameBool = true;
}
}
}
else
{
NameBool = true;
}
return subBool && NameBool;
}
}
catch (System.Runtime.InteropServices.COMException ce)
{
if (ce.ErrorCode == -2147467259)
{
//DO nothing just move to the next one
}
else
{
MessageBox.Show("Crash in IsMatch error code = " + ce.ErrorCode + " " + ce.InnerException);
}
}
return false;
}
Please excuse all the error catching part at the bottom and the GC.collect they are some of my attempts to work out what is wrong and free up memory.
Note also FindInFolder is called by a new thread so I can interact with results while it continues to search.
What I have tried so far:
Making variables local to function not class so the are retrievable by G, however the most used variable in 'item' as it is part of foreach it must be declared that way.
every 1000 mailItems do a manual GC, this made no difference at all.
For some reason it needs alot of memory just looping through the items and GC never frees them up.
Please also note I am using netoffice not VSTO for Com addin.

First of it all: This is NetOffice code and you dont need Marshal.ReleaseComObject in NetOffice. (Moreover, its useless to call ReleaseComObject here) Use Dispose() for the instance instead.
Keep in your mind: NetOffice handle COM proxies for you (Thats why its allowed to use two 2 dots in NetOffice).
In your case its internaly stored as:
// FolderToSearch
-- Items
--Enumerator
-- Item
-- Item
-- ....
Use item.Dispose() at the end of each loop to remove/free the item instance or use the following after foreach
FolderToSearch.Dipose()
// dispose folder instance and all proxies there comes from
FolderToSearch.DisposeChildInstances()
// dispose all proxies there comes from but keep the folder instance alive
Next:
The Items enumerator here is a custom enumerator(given by NetOffice)
However it works fine with small amount of items but dont do this in a more
heavier scenario(may exchange server and thousands of items). The local workstation/program can't handle this in memory. For this reason Microsoft provides only the well kown GetFirst/GetNext pattern. In NetOffice it looks like:
Outlook._Items items = FolderToSearch.Items;
COMObject item = null;
do
{
if (null == item)
item = items.GetFirst() as COMObject;
if (null == item)
break;
// do what you want here
item.Dispose();
item = items.GetNext() as COMObject;
} while (null != item);
This should works as well.

When working with COM objects from C#, there were 2 tricks that I used to prevent memory and COM object reference counts from building:
Use System.Runtime.InteropServices.Marshal.ReleaseComObject() to release COM objects as you are done with them. This forces a COM "release" on the object.
Do not foreach to iterate through a COM collection. foreach holds on to an enumerator object, which prevents other objects from being released.
So, instead of this:
foreach (COMObject item in FolderToSearch.Items)
{
// ....
}
do this:
Items items = FolderToSearch.Items;
try
{
for (int i = 0; i < items.Count; ++i)
{
COMObject item = items[i];
try
{
// work
}
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(item);
}
}
}
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(items);
}
These tips helped me reduce memory and object consumption.
Disclaimer: I cannot attest to whether this is good practice or not though.

First, I'd recommend using the Find/FindNext or Restrict methods of the Items class instead of iterating through all items in the folder. For example:
Sub DemoFindNext()
Dim myNameSpace As Outlook.NameSpace
Dim tdystart As Date
Dim tdyend As Date
Dim myAppointments As Outlook.Items
Dim currentAppointment As Outlook.AppointmentItem
Set myNameSpace = Application.GetNamespace("MAPI")
tdystart = VBA.Format(Now, "Short Date")
tdyend = VBA.Format(Now + 1, "Short Date")
Set myAppointments = myNameSpace.GetDefaultFolder(olFolderCalendar).Items
Set currentAppointment = myAppointments.Find("[Start] >= """ & tdystart & """ and [Start] <= """ & tdyend & """")
While TypeName(currentAppointment) <> "Nothing"
MsgBox currentAppointment.Subject
Set currentAppointment = myAppointments.FindNext
Wend
End Sub
See the following articles for more information and sample code:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Also you may find the AdvancedSearch method of the Application class helpful. The key benefits of using the AdvancedSearch method are listed below:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
Second, I always suggest releasing underlying COM objects instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. You can read more about that in the Systematically Releasing Objects article.
If you want to use GC, you need to call the Collect and WaitForPendingFinalizers methods twice.
Matt, you still don't release all objects in the code. For example:
for (int i = 0; i < FolderToSearch.Items.Count; ++i)
{
COMObject item = FolderToSearch.Items[i];
The Items property of the Folder class returns an instance of the corresponding class which should be released after. I see at lease two lines of code where the reference counter is increased.

Related

Session.Remove("session_variable"); not working?

Hi in the following stuff am trying to remove the session after completing the certain tasks. but the session.remove() not working here.
if (Session["CaseNumber"] != "" || Session["CaseNumber"] != null)
{
casenumber = Session["CaseNumber"].ToString();
guid = Session["guid"].ToString();
_duration = bal.viewServiceActivity(guid);
case1 = bal.viewCaseDetail(casenumber);
dt = bal.getRelatedNotes(guid);
Session.Remove("CaseNumber");
Session.Remove("guid");
}
else
{
casenumber = Session["searchCase"].ToString();
guid = Session["caseGuid"].ToString();
_duration = bal.viewServiceActivity(guid);
case1 = bal.viewCaseDetail(casenumber);
dt = bal.getRelatedNotes(guid);
Session.Remove("searchCase");
Session.Remove("caseGuid");
}
There's nothing really wrong with portion of your code shown above. Just understanding is required about how it works.
Session.Remove(key) removes memory used for the key in the session dictionary. When using second approach of : Session["searchCase"]=null; , the key will still exist in the Session although the value is null.
Try setting the Session["Key_Name"] to null. For example:
if (Session["CaseNumber"] != "" || Session["CaseNumber"] != null)
{
casenumber = Session["CaseNumber"].ToString();
guid = Session["guid"].ToString();
_duration = bal.viewServiceActivity(guid);
case1 = bal.viewCaseDetail(casenumber);
dt = bal.getRelatedNotes(guid);
Session["CaseNumber"] = null; // Set NULL
Session["guid"] = null; // Set NULL
}
Session.Remove() doesn't destroys the object bypassing all basic .net rules of object referencing and memory management. What you need to understand here is that Session.Remove() does exactly what it suggests: It removes the reference to the object from its internal collection. Nothing more to expect.
If you need something destroyed, you need to implement IDisposable, and furthermore, you may instruct the garbage collector to collect if you are in a hurry using GC.Collect().
Check this SO question: Proper use of the IDisposable interface
Microsoft Forum confirms the same.
It seems more like an optimization OR so called standard way of implementation.
When you remove an object it is 'marked for removal' but not really removed...unless space is required.
It's how garbage collection works. Just because an object goes out of scope doesn't mean it's destructor gets called and the memory freed immediately. May be GC can remove an object much later.
Still, as a last check, do this :
Make sure you are not viewing a cached version of the page, or you don't add the item to the Session object again somewhere in your application.

strange behavior of XamlReader.Load()?

I've got a very strange issue while parsing an external XAML file. The pre-history is that I want to load an external XAML file with content to process. But I want to load as many different files as I want. That happens by unloading the old and loading the new one.
My issue is:
When I load a xaml the first time, everything is good, all as it should be.
But when I load the same xaml the second time, every entry of the object im Loading is there twice. If I run this again, every object is there three times and so on...
To debug the project yourself, download it here. The function starts at line 137 in the file "Control Panel.xaml.cs". I realy don't know what this is. Is it my fault or simply a bug? If yes, is there a workaround?
/// <summary>
/// Load a xaml file and parse it
/// </summary>
public void LoadPresentation()
{
this.Title = "Control Panel - " + System.IO.Path.GetFileName(global.file);
System.IO.FileStream XAML_file = new System.IO.FileStream(global.file, System.IO.FileMode.Open);
try
{
System.IO.StreamReader reader = new System.IO.StreamReader(XAML_file);
string dump = reader.ReadToEnd(); //This is only for debugging purposes because of the strange issue...
XAML_file.Seek(0, System.IO.SeekOrigin.Begin);
presentation = (ResourceDictionary)XamlReader.Load(XAML_file);
//Keys the resourceDictionary must have to be valid
if (presentation["INDEX"] == null || presentation["MAIN_GRID"] == null || presentation["CONTAINER"] == null || presentation["LAYOUTLIST"] == null)
{
throw new Exception();
}
//When this list is loaded, every item in it is there twice or three times or four... Why????
TopicList Index = null;
Index = (TopicList)presentation["INDEX"];
for (int i = 0; i < topics.Count; )
{
topics.RemoveAt(i);
}
foreach (TopicListItem item in Index.Topics)
{
topics.Insert(item.TopicIndex, (Topic)presentation[item.ResourceKey]);
}
lv_topics.SelectedIndex = 0;
selectedIndex = 0;
}
catch
{
System.Windows.Forms.MessageBox.Show("Failed to load XAML file \"" + global.file + "\"", "Parsing Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
presentation = null;
}
finally
{
XAML_file.Close();
}
}
Edit:
I have tried to serialize the object that was read from the XamlReader and in the output was nowhere any childelement... But if I pull the object out of the dictionary, the children are all there (duplicated and triplicated, but there).
I have already tried to clear the list over
topics.Clear();
and
topics=new ObservableCollection<TopicListItem>();
lv_topics.ItemsSource=topics;
Try Index.Topics.Clear() after loading the Topics into your topics object. That appears to get rid of the duplication.
//When this list is loaded, every item in it is there twice or three times or four... Why????
TopicList Index = null;
Index = (TopicList)presentation["INDEX"];
topics.Clear();
foreach (TopicListItem item in Index.Topics)
{
topics.Insert(item.TopicIndex, (Topic)presentation[item.ResourceKey]);
}
Index.Topics.Clear(); //Adding this will prevent the duplication
lv_topics.SelectedIndex = 0;
selectedIndex = 0;
In the code post topics is not declared in LoadPresentation() so naturally it will have any prior values.
I know you said you tried topics=new ObservableCollection(); but please try again. And put that IN LoadPresentation()
public void LoadPresentation()
{
ObservableCollection<TopicListItem> topics = new ObservableCollection<TopicListItem>()
I would pass filename
public void LoadPresentation(string fileName)
I get you may need to use topics outside LoadPresentation but this is debugging. If you need topics outside the return it.
public ObservableCollection<TopicListItem> LoadPresentation(string fileName)
If that does not fix it I would put a try catch block on the XAML_file.Close(); to see if something weird is not going on.

Recursive conditions

Sorry to put up yet another recursion question, but I've looked over a fair few on here and haven't found the solution for my problem.
I use the below function:
unsafe
{
// Allocate global memory space for the size of AccessibleContextInfo and store the address in acPtr
IntPtr acPtr = Marshal.AllocHGlobal(Marshal.SizeOf(new AccessibleContextInfo()));
try
{
Marshal.StructureToPtr(new AccessibleContextInfo(), acPtr, true);
if (WABAPI.getAccessibleContextInfo(vmID, ac, acPtr))
{
acInfo = (AccessibleContextInfo)Marshal.PtrToStructure(acPtr, typeof(AccessibleContextInfo));
if (!ReferenceEquals(acInfo, null))
{
AccessibleTextItemsInfo atInfo = new AccessibleTextItemsInfo();
if (acInfo.accessibleText)
{
IntPtr ati = Marshal.AllocHGlobal(Marshal.SizeOf(new AccessibleTextItemsInfo()));
WABAPI.getAccessibleTextItems(vmID, ac, ati, 0); //THIS IS WHERE WE DO IT
atInfo = (AccessibleTextItemsInfo)Marshal.PtrToStructure(ati, typeof(AccessibleTextItemsInfo));
if (ati != IntPtr.Zero)
{
Marshal.FreeHGlobal(ati);
}
}
AccessibleTreeItem newItem = BuildAccessibleTree(acInfo, atInfo, parentItem, acPtr);
newItem.setAccessibleText(atInfo);
if (!ReferenceEquals(newItem, null))
{
for (int i = 0; i < acInfo.childrenCount; i++)
{
//Used roles = text, page tab, push button
if (acInfo.role_en_US != "unknown" && acInfo.states_en_US.Contains("visible")) // Note the optomization here, I found this get me to an acceptable speed
{
AccessibleContextInfo childAc = new AccessibleContextInfo();
IntPtr childContext = WABAPI.getAccessibleChildFromContext(vmID, ac, i);
GetAccessibleContextInfo(vmID, childContext, out childAc, newItem);
if (childContext != IntPtr.Zero)
{
Settings.Save.debugLog("Releasing object " + childContext.ToString() + " from JVM: " + vmID);
WABAPI.releaseJavaObject(vmID, childContext);
childContext = IntPtr.Zero;
}
}
}
}
return newItem;
}
}
else
{
acInfo = new AccessibleContextInfo();
}
}
finally
{
if (acPtr != IntPtr.Zero)
Marshal.FreeHGlobal(acPtr);
}
}
return null;
}
To build an AccessibleTreeItem representing the entire GUI of a Java application. However, this function takes 5-6 seconds to run. I'm only looking for one particular subsection of the tree (Lets call it Porkchops).
What I'd like to do is prior to building the tree, get the values and as soon as acRole.name == "Porkchop", use that as the parent object and create an AccessibleTreeItem that represents the subtree.
How on earth do I manage this? If this is a simple question, apologies, but it's driving me crazy.
Edit 1 - The performance hit is encountered on releaseJavaObject(), as when I remove that line the function completes in less than a second, but it creates a horrible memory leak.
Therefore, I'm not really looking for alternative solutions, as I know that the above does work correctly. I just need some way to check the value of acInfo.name prior to creating the tree, and then using the correct acInfo node as the parent.
Edit 2 - See the attached image for a better explanation than my rambling. Currently, the function will pull this entire tree from the JVM. I've highlighted the appropriate section that I work with, and would like to know if there's a way that will allow me to get that information, without building the entire tree. Or even if I could just return the tree once all children of that node have been populated.

IEnumerable not enumerating in foreach

I'm encountering a problem with one of my IEnumerable's that I haven't seen before.
I have a collection:
IEnumerable<IDependency> dependencies;
that's being used in a foreach loop.
foreach (var dependency in dependencies)
For some reason this foreach doesn't iterate over my IEnumerable and simply skips to the end.
If I change my foreach to loop through a a list however it seems to work fine:
foreach (var dependency in dependencies.ToList())
What could I be doing that's causing this behaviour? I haven't experienced this with IEnumerable before.
Update:
Here's the entire code of my foreach that's running in my method GenerateDotString:
foreach (var dependency in dependencies)
{
var dependentResource = dependency.Resource;
var lineColor = (dependency.Type == DependencyTypeEnum.DependencyType.Hard) ? "blue" : "red";
output += labelFormat.FormatWith(dependentResource.Name.MakeDotsafeString(), dependentResource.Name, dependentResource.ResourceType);
output += relationshipFormat.FormatWith(dependentResource.Name.MakeDotsafeString(), currentName, lineColor);
if (dependentResource.DependentResources != null)
{
output += GenerateDotString(dependentResource, dependentResource.DependentResources, searchDirection);
}
}
return output;
Update 2:
Here's the signature of the method containing this foreach (incase it helps).
private static string GenerateDotString(IResource resource, IEnumerable<IDependency> dependencies, SearchEnums.SearchDirection searchDirection)
Update 3:
Here's the method GetAllRelatedResourcesByParentGuidWithoutCacheCheck:
private IEnumerable<IDependency> GetAllRelatedResourcesByParentGuidWithoutCacheCheck(Guid parentCiGuid, Func<Guid, IEnumerable<IDependency>> getResources)
{
if (!_itemsCheckedForRelations.Contains(parentCiGuid)) // Have we already got related resources for this CI?;
{
var relatedResources = getResources(parentCiGuid);
_itemsCheckedForRelations.Add(parentCiGuid);
if (relatedResources.Count() > 0)
{
foreach (var relatedResource in relatedResources)
{
relatedResource.Resource.DependentResources = GetAllRelatedResourcesByParentGuidWithoutCacheCheck(relatedResource.Resource.Id, getResources);
yield return relatedResource;
}
}
}
}
Update 4:
I'm adding the methods in the chain here to be clear on how we're getting the collection of dependencies.
The above method GetAllRelatedResourcesByParentGuidWithoutCacheCheck accepts a delegate which in this case is:
private IEnumerable<IDependency> GetAllSupportsResources(Guid resourceId)
{
var hardDependents = GetSupportsHardByParentGuid(resourceId);
var softDependents = GetSupportsSoftByParentGuid(resourceId);
var allresources = hardDependents.Union(softDependents);
return allresources;
}
which is calling:
private IEnumerable<IDependency> GetSupportsHardByParentGuid(Guid parentCiGuid)
{
XmlNode ciXmlNode = _reportManagementService.RunReportWithParameters(Res.SupportsHardReportGuid, Res.DependentCiReportCiParamName + "=" + parentCiGuid);
return GetResourcesFromXmlNode(ciXmlNode, DependencyTypeEnum.DependencyType.Hard);
}
and returns:
private IEnumerable<IDependency> GetResourcesFromXmlNode(XmlNode ciXmlNode, DependencyTypeEnum.DependencyType dependencyType)
{
var allResources = GetAllResources();
foreach (var nodeItem in ciXmlNode.SelectNodes(Res.WebServiceXmlRootNode).Cast<XmlNode>())
{
Guid resourceGuid;
var isValidGuid = Guid.TryParse(nodeItem.SelectSingleNode("ResourceGuid").InnerText, out resourceGuid);
var copyOfResource = allResources.Where(m => m.Id == resourceGuid).SingleOrDefault();
if (isValidGuid && copyOfResource != null)
{
yield return new Dependency
{
Resource = copyOfResource,
Type = dependencyType
};
}
}
}
which is where the concrete type is returned.
So it looks like the problem was to do with the dependencies collection infinately depending on itself.
It seems from my debugging that iterating the IEnumerable causes a timeout and so the foreach simply skips execution of its contents where as ToList() returns as much as it can before timing out.
I may not be correct about that but it's what seems to be the case as far as I can tell.
To give a bit of background as to how this all came about I'll explain the code changes I made yesterday.
The first thing the application does is build up a collection of all resources which are filtered by resource type. These are being brought in from our CMDB via a web service call.
What I was then doing is for each resource that was selected (via autocomplete in this case) I'd make a web service call and get the dependents for the resource based on its Guid. (recursively)
I changed this yesterday so that we didn't need to obtain the full resource information in this second web service call, rather, simply obtain a list of Guids in the web service call and grab the resources from our resources collection.
What I forgot was that the web service call for dependents wasn't filtered by type and so it was returning results that didn't exist in the original resources collection.
I need to look a bit further but it seems that at this point, the new collection of dependent resources was becoming dependent on itself and thus, causing the IEnumerable<IDependents> collection later on to timeout.
This is where I've got to today, if I find anything else I'll be sure to note it here.
To summarise this:
If infinite recursion occurs in an IEnumerable it'll simply timeout
when attempting to enumerate in a foreach.
Using ToList() on the IEnumerable seems to return as much data as it
can before timing out.

How to release Outlook objects correctly?

I just can't release my Outlook MailItems. After opening 200 Mails the Exchange Sever returns the maximum open Emails is reached.
I'm remove my UserProperty from all selected Mail.
My Code:
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
if (selection is MailItem)
{
MailItem mi = (MailItem)selection;
UserProperty up = mi.UserProperties.Find("MyProp");
if (up != null)
{
up.Delete();
//##################################
// I also tried :
//----------------------------------
// Marshal.ReleaseComObject(up);
// up = null;
//----------------------------------
}
mi.Save();
//##################################
// I also tried :
//----------------------------------
// mi.Close(OlInspectorClose.olDiscard);
//----------------------------------
// I don't know if this loop is necessary, but I have found it somewhere on the web
while (Marshal.ReleaseComObject(mi) > 0);
mi = null;
//##################################
// I also tried :
//----------------------------------
// GC.Collect();
// GC.WaitForPendingFinalizers();
//----------------------------------
}
}
Any idea what's wrong?
You can try this: Instead of defining a new MailItem everytime within the For loop, can you define mi outside the For loop or even in your class level, and reuse it for each mailitems? e.g:
MailItem mi;
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
if (selection is MailItem)
{
mi= (MailItem)selection;
// your other code...
}
}
mi=null;
GC.Collect();
GC.WaitForPendingFinalizers();
EDIT:
Try to create local variable for each references e.g:
Outlook.Explorer myExplorer=Application.ActiveExplorer();
Outlook.Selection mySelection=myexplorer.Selection;
foreach (var selection in mySelection)
{
}
myExplorer=null;
mySelection=null;
//....
EDIT-2:
IF you are using Outlook 2010 check this:
Outlook 2010 addin selection not clearing
I believe its any kind of bug like Bolu said.
Again much thanks for your help Bolu.
I'm now using following workaround:
List entryids = new List();
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
MailItem mi = selection as MailItem;
if (mi != null)
{
// For any reason it's not possible to change the mail here
entryids.Add(mi.EntryID);
Marshal.ReleaseComObject(mi);
mi = null;
}
}
foreach (string id in entryids)
{
MailItem mi = Globals.ThisAddIn.Application.ActiveExplorer().Session.GetItemFromID(id);
// My changes on the mail
mi.Save();
Marshal.ReleaseComObject(mi);
mi = null;
}
You are using multiple dot notation (mi.UserProperties.Find), which means the compiler creates an implicit variable to hold the result of the mi.UserProperties call; you cannot explicitly release that variable. That object holds a reference to its parent MailItem object.
Store it in an explicit variable and release it explicitly using Marshal.ReleaseComObject. Ditto for the UserProperty up variable.
Also, do not use foreach with Outlook collections - that loop holds a reference to all items until the loop exits. Use a for loop and release the items explicitly on each step of the loop immediately after you are done with that item
Selection selectedItems = Globals.ThisAddIn.Application.ActiveExplorer().Selection;
for (int i = 1; i <= selectedItems.Count; i++)
{
object selection = selectedItems[i];
MailItem mi = selection as MailItem;
if (mi != null) //can have items other than MailItem
{
UserProperties props = mi.UserProperties;
UserProperty up = props.Find("MyProp");
if (up != null)
{
...
Marshal.ReleaseComObject(up);
};
Marshal.ReleaseComObject(props);
Marshal.ReleaseComObject(mi);
}; //if
Marshal.ReleaseComObject(selection);
}; //for
Try to replace foreach with a for loop and do the following
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Also remove all the reference to any outlook COM object that you might be using.
Just put the releasing method outside the main if condition. Items get referenced even when you just loop through them with the foreach loop.
Write
Marshal.ReleaseComObject(mi)
right before the last "}". This works for me. I read that generally you have to release every COM object explicitly.

Categories

Resources