'C# Outlook Add-in optimization

I asked here recently about VBA code in Outlook, I was advised to create an Add-in to distribute my code. I learn a bit of C# and created my Outlook Add-in. After a lot of learning I managed to get it working, but now I'm having trouble with optimisation. This code goes through the Tasks folder and the Contacts folder searching for a specific userproperty and then delete those items.

I'm running my add-in on about 900 items and the task is completed in a few seconds, then I tried to get a bigger PST file and run my code on about 10 000 items, it takes about 10 minutes. The memory keeps going up until it reaches about 500Mo. The machine I'm running this on is pretty good so I'm afraid when a client will run it on a slow computer it will take forever.

I would greatly appreciate any help on this project, I'm just starting on C# and I'm not a programmer, I was able to google my way through this but it has it's limits.

Thanks a lot!

Here's the code that happens when the button is clicked:

    private void button1_Click(object sender, EventArgs e)
    {
        label2.Visible = true;
        listBox1.Visible = false;
        label4.Visible = false;
        button1.Enabled = false;
        Update();            
        Outlook.Store goodStore = null;

        string selStore = listBox1.SelectedItem.ToString();

        foreach (Outlook.Store store in Globals.ThisAddIn.Application.Session.Stores)
        {
            string exType = store.ExchangeStoreType.ToString();
            if (exType != "olExchangePublicFolder")
            {
                if (Strings.InStr(store.DisplayName.ToString(), selStore) > 0)
                {
                    goodStore = store;
                }
            }
        }

        if (goodStore == null)
        {
            MessageBox.Show("Error");
            string keyname = @"HKEY_CURRENT_USER\Software\Microsoft\Office\Outlook\Addins\TACleanUpAddin";
            Registry.SetValue(keyname, "LoadBehavior", "0", RegistryValueKind.DWord);
            Environment.Exit(1);
        }

        Outlook.Folder taskFolder = (Outlook.Folder)goodStore.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderTasks);
        Outlook.Folder contactFolder = (Outlook.Folder)goodStore.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
        Outlook.Folder deletedFolder = (Outlook.Folder)goodStore.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderDeletedItems);
        string uProperty = "crmxml";
        string uPropertyCLS = "crmLinkState";
        Outlook.UserProperty objProperty;
        Outlook.UserProperty objPropertyCLS;
        List<Outlook.TaskItem> tlist = new List<Outlook.TaskItem>();
        List<Outlook.ContactItem> clist = new List<Outlook.ContactItem>();

        foreach (Outlook.TaskItem ti in taskFolder.Items)
        {
            objProperty = ti.UserProperties.Find(uProperty, Outlook.OlUserPropertyType.olText);
            if (objProperty == null)
            {
                Debug.Print("prop is null!");
            }
            else
            {
                string strProperty = objProperty.Value.ToString();
                if (Strings.InStr(strProperty, "<phonecall><activityid>") > 0)
                {
                    tlist.Add(ti);
                }
                else if (Strings.InStr(strProperty, "<letter><activityid>") > 0)
                {
                    tlist.Add(ti);
                }
                else if (Strings.InStr(strProperty, "<fax><activityid>") > 0)
                {
                    tlist.Add(ti);
                }
            }
        }

        foreach (Outlook.ContactItem ci in contactFolder.Items)
        {
            objPropertyCLS = ci.UserProperties.Find(uPropertyCLS, Outlook.OlUserPropertyType.olNumber);
            if (objPropertyCLS == null)
            {
                Debug.Print("propCLS is null!");
            }
            else
            {
                clist.Add(ci);
            }
        }

        foreach (Outlook.TaskItem ti in tlist)
        {
            ti.Delete();
        }

        foreach (Outlook.ContactItem ci in clist)
        {
            ci.Delete();
        }

        label2.Visible = false;
        label3.Visible = true;
        button2.Enabled = true;
    }


Solution 1:[1]

Firstly, do no loop through all items in a folder, use Items.Find/FindNext or Items.Restrict.

Secondly, do not store live Outlook items in a list - you can run out of RPC channels in case of an online Exchange store. Either process the items immediately and release them using Marshal.ReleaseComObject or store the entry ids in a list and reopen the items later using Application.Session.GetItemFromID (using Marshal.ReleaseComObject is still a good idea).

Thirdly, make sure you are not using multiple dot notation, especially in a loop.

string query = "@SQL=\"http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/MyPropName/0x0000001F\" = 'SomeValue' "
Outlook.Items items = taskFolder.Items;
Outlook.TaskItem ti = items.Find(query);
while (ti != null)
{
   //do something 
   ti = items.FindNext();
}

The actual value of your property's DASL name (see above) can be retrieved using OutlookSpy - select an item with that property set, click IMessage button on the OutlookSpy ribbon, find the property, see the "DASL Name" edit box.

Your next optimization could be moving the search into a separate thread. Unfortunately Outlook Object Model cannot be used from a secondary thread in an addin, so it is either Extended MAPI (C++ or Delphi) or Redemption (I am its author) with its RDO family of objects.

Solution 2:[2]

I suggest that you delete the items that you want to delete immediately in the original loops. I.e, don't add them to a list and delete them later.

Adding them to a list will prevent the garbage collector from collecting their memory and their unmanaged resources, which explains why you are consuming too much memory.

I am not sure, but I guess that deleting items immediately might corrupt the iterator (of taskFolder.Items). If this is the case, then simply iterate through the items starting with the last one and ending with the first one like this:

for (int i = contactFolder.Items.Count; i >= 1; i--)
{
    Outlook.ContactItem ci = contactFolder.Items[i];

    objPropertyCLS = ci.UserProperties.Find(uPropertyCLS, Outlook.OlUserPropertyType.olNumber);
    if (objPropertyCLS == null)
    {
        Debug.Print("propCLS is null!");
    }
    else
    {
        ci.Delete();
    }         
}

Please note that collections in Outlook Object Model start with index 1 and not 0.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2