Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/300.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 防止在Outlook VSTO中重复调用事件处理程序_C#_Events_Outlook_Outlook Addin - Fatal编程技术网

C# 防止在Outlook VSTO中重复调用事件处理程序

C# 防止在Outlook VSTO中重复调用事件处理程序,c#,events,outlook,outlook-addin,C#,Events,Outlook,Outlook Addin,我正在为Outlook 2010编写一个VSTO(尽管它需要在2010-2016年运行,而且大部分情况下是这样)。我遇到了一个奇怪的问题,据我所知,事件处理程序从未被删除。这意味着我无法避免重复调用事件处理程序,这既愚蠢又浪费 有问题的代码发生在浏览器的SelectionChange事件的事件处理程序中。处理程序检查所选内容是否为邮件项,如果是,则确保回复、回复和转发事件具有处理程序。由于给定的项目可能会被选择多次,因此SelectionChange处理程序首先删除Reply/ReplyAll/

我正在为Outlook 2010编写一个VSTO(尽管它需要在2010-2016年运行,而且大部分情况下是这样)。我遇到了一个奇怪的问题,据我所知,事件处理程序从未被删除。这意味着我无法避免重复调用事件处理程序,这既愚蠢又浪费

有问题的代码发生在浏览器的
SelectionChange
事件的事件处理程序中。处理程序检查所选内容是否为
邮件项
,如果是,则确保
回复
回复
转发
事件具有处理程序。由于给定的项目可能会被选择多次,因此
SelectionChange
处理程序首先删除
Reply
/
ReplyAll
/
转发事件处理程序,与所示模式保持一致(当您不控制事件承载类实现时,防止事件处理程序被钩住两次)

问题是,这并没有阻止
Reply
(或其他响应操作)事件处理程序在
SelectionChange
事件处理程序触发的每个实例中调用一次。这很快就达到了愚蠢的调用次数。我认为这可能是一个同步问题,所以我包装了事件处理程序的删除,并添加了一个
块,但没有效果

    private void SelectionChangeHandler()
    {
        Outlook.Selection sel = Application.ActiveExplorer().Selection;
        // First make sure it's a (single) mail item
        if (1 != sel.Count)
        {   // Ignore multi-select
            return;
        }
        // Indexed from 1, not 0. Stupid VB-ish thing...
        Outlook.MailItem mail = sel[1] as Outlook.MailItem;
        if (null != mail)
        {
            Outlook.ItemEvents_10_Event mailE = mail as Outlook.ItemEvents_10_Event;
            lock (this)
            {   // For each event, remove the handler then add it again
                mailE.Forward -= MailItemResponseHandler;
                mailE.Forward += MailItemResponseHandler;
                mailE.Reply -= MailItemResponseHandler;
                mailE.Reply += MailItemResponseHandler;
                mailE.ReplyAll -= MailItemResponseHandler;
                mailE.ReplyAll += MailItemResponseHandler;
            }
            ProcessMailitem(mail);
        }
    }
以及被多次调用的事件处理程序:

    private void MailItemResponseHandler (object newItem, ref bool Cancel)
    {   // We need to get the responded-to item
        // NOTE: There really needs to be a better way to do this
        Outlook.MailItem old = GetCurrentMail();
        if (null == old)
        {   // No mail item selected
            return;
        }
        MessageBox.Show(old.Body);
    }
这个函数最终会做一些比弹出对话框更有用的事情,但这是一个方便的检查“我找到正确的原始消息了吗?”。不过,我不应该一次又一次地看到同一个对话框,我是


我做错什么了吗?这是Outlook中的错误还是VSTO中的错误?有人知道如何避免重复的事件处理程序调用吗?

首先,mailE变量必须在类级别声明,而不是本地声明,以防止它被垃圾收集


其次,在设置事件之前,必须使用Marshal.ReleaseComObject释放旧值(mailE)

事件处理程序实际上没有附加到Outlook使用的MAPI项目。相反,它被附加到一个名为(RCW)的.NET对象,该对象包装一个COM对象。由于RCW的工作方式,获取对似乎是同一对象的多个引用(例如,通过两次获取
activeExplorer.Selection()[1]
)会在不同的COM对象周围提供多个RCW。这意味着我试图从中删除事件的
Outlook.MailItem
(或
Outlook.ItemEvents\u 10\u Event
)实际上没有任何事件;每次启动
SelectionChangeHandler
时,它都是新创建的

与此相关的是,由于对RCW包装的COM对象的唯一引用是RCW本身,因此让所有引用RCW的变量超出范围(或停止引用该RCW)将导致RCW被垃圾收集(在此期间,COM对象将被释放和删除)。这对相关规范有两个相关影响:

  • 由于垃圾收集不是立即进行的,因此带有现有事件处理程序的旧RCW(和COM对象)仍在运行,Outlook仍可能在其COM对象上触发事件。这就是为什么会多次调用事件处理程序
  • 因为RCW超出了
    SelectionChangeHandler
    底部的范围,所以垃圾收集清除所有RCW(和事件处理程序)并释放其所有COM对象只是时间问题。在这一点上,没有事件将附加到该电子邮件。
    • 在实践中,我的测试在一个足够短的时间内进行,因此我更有可能获得多个实时RCW,而不是一个都没有,选择一个邮件项目并且不与之交互(或选择任何其他内容)足够长的时间以触发垃圾收集清理,事实上,结果当我单击“回复”时,
      MailItemResponseHandler
      根本没有被调用
  • @DmitryStreblechenko给了我正确的方向去解决这个问题,但这需要一些实验来解决。首先,需要全局引用相关的
    MailItem
    ,这样它对事件RCW的引用就不会超出范围,同样重要的是,当再次调用
    SelectionChangeHandler
    时,它的RCW仍然可以直接引用。我重命名了变量
    selectedMail
    ,并在类级别引用它,如下所示:

    Outlook.ItemEvents_10_Event selectedMail;
    
    然后,我修改了
    SelectionChangeHandler
    ,以便在使用当前选定的单个
    MailItem
    调用它时,它首先从
    selectedMail
    中删除所有事件处理程序,然后才将
    selectedMail
    指向新选定的项目。先前由
    selectedMail
    引用的RCW有资格进行垃圾收集,但它没有事件处理程序,因此我们并不关心
    SelectionChangeHandler
    然后将相关事件处理程序添加到现在由
    selectedMail
    引用的新RCW中

        private void SelectionChangeHandler()
        {
            Outlook.Selection sel = activeExplorer.Selection;
            // First make sure it's a (single) mail item
            if (1 != sel.Count)
            {   // Ignore multi-select
                return;
            }
            // Indexed from 1, not 0. Stupid VB-ish thing...
            Outlook.MailItem mail = sel[1] as Outlook.MailItem;
            if (null != mail)
            {
                if (null != selectedMail)
                {   // Remove the old event handlers, if they were set, so there's no repeated events
                    selectedMail.Forward -= MailItemResponseHandler;
                    selectedMail.Reply -= MailItemResponseHandler;
                    selectedMail.ReplyAll -= MailItemResponseHandler;
                }
                selectedMail = mail as Outlook.ItemEvents_10_Event;
                selectedMail.Forward += MailItemResponseHandler;
                selectedMail.Reply += MailItemResponseHandler;
                selectedMail.ReplyAll += MailItemResponseHandler;
                if (DecryptOnSelect)
                {   // We've got a live mail item selected. Process it
                    ProcessMailitem(mail);
    
                }
            }
        }
    
    根据Dmitri的回答和评论,我尝试调用
    selectedMail
    的旧值的
    Marshal.ReleaseComObject(selectedMail)
    ,然后才考虑删除事件处理程序。这有点帮助,但COM对象不会立即发布,或者Outlook仍然可以通过它们调用事件处理程序,因为如果在点击回复之前的短时间内多次选择给定电子邮件,事件仍会多次触发

    还有一个小问题需要解决。如果我打开一个inspector并在那里点击Reply,而不更改资源管理器中的选择,那么它可以正常工作(调用
    MailItemResponseHandler
    )。但是,如果我让inspector保持打开状态,切换回explorer并选择其他电子邮件,然后返回inspector并点击Reply,则不会