【问题标题】:How to correctly reset a custom event in C#?如何正确重置 C# 中的自定义事件?
【发布时间】:2016-05-30 20:55:18
【问题描述】:

我有这个代码:

private void loadGENIOFileToolStripMenuItem_Click(object sender, EventArgs e)
{
    OpenFileDialog dlgFile = new OpenFileDialog();

    dlgFile.InitialDirectory = Properties.Settings.Default.PreviousPath;
    dlgFile.Title = "Select GENIO file";
    dlgFile.Filter = "GENIO files (*.txt)|*.txt";
    dlgFile.FilterIndex = 0;
    dlgFile.Multiselect = false;

    if (dlgFile.ShowDialog() == DialogResult.OK)
    {
        Properties.Settings.Default.PreviousPath = Path.GetDirectoryName(dlgFile.FileName);

        DeleteView();

        m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
        {
            mruMenu.AddFile(dlgFile.FileName);
            m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
            CreateView();
        };

        m_oThreadServices.SetGenioFilePath(dlgFile.FileName);
        m_oThreadServices.start();
    }
}

但我也在尝试实现 MRU 处理程序:

    private void OnMruFile(int number, String filename)
    {
        if (File.Exists(filename))
        {
            Properties.Settings.Default.PreviousPath = Path.GetDirectoryName(filename);

            DeleteView();

            m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
            {
                mruMenu.SetFirstFile(number);
                m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
                CreateView();
            };

            m_oThreadServices.SetGenioFilePath(filename);
            m_oThreadServices.start();
        }
        else
            mruMenu.RemoveFile(number);
    }
}

我的m_oThreadServices.OnLoadingCompleted 代码行似乎要求我使用+=,因此,如果我首先加载 一个文件,它会添加第一个事件处理程序。如果我然后去使用 MRU 列表来加载不同的文件,它最终会运行 两个 OnLoadingCompleted 处理程序。

我试过m_oThreadServices.OnLoadingCompleted =,但它不允许。那么我拦截事件处理程序而不是最终调用两组代码的正确方法是什么?我是不是搞错了?

谢谢。

【问题讨论】:

    标签: c# winforms custom-events


    【解决方案1】:

    一旦引发事件,您应该确保从事件源取消订阅您的事件处理程序。

    为此,您必须稍微修改匿名处理程序。比如这个sn-p:

    m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
    {
        mruMenu.AddFile(dlgFile.FileName);
        m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
        CreateView();
    };
    

    应该是这样的:

    EventHandler onLoadingCompleted = null;
    onLoadingCompleted = (_sender, _e) =>
    {
        m_oThreadServices.OnLoadingCompleted -= onLoadingCompleted;
        mruMenu.AddFile(dlgFile.FileName);
        m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
        CreateView();
    };
    m_oThreadServices.OnLoadingCompleted += onLoadingCompleted;
    

    另一个也一样。

    线

    EventHandler onLoadingCompleted = null;
    

    需要避免在此处使用未初始化的变量编译器错误

    m_oThreadServices.OnLoadingCompleted -= onLoadingCompleted;
    

    【讨论】:

    • 谢谢。我会在早上试试这个。
    • 这完全符合我的要求,感谢您的解释。再次感谢!
    【解决方案2】:

    如果处理程序是命名函数,您可以删除它:

    private void OnLoadingComplete_AddFile(_sender, _e) 
    {
        mruMenu.AddFile(dlgFile.FileName);
        m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
        CreateView();
    }
    
    ...
    
    m_oThreadServices.OnLoadingCompleted += OnLoadingComplete_AddFile;
    
    ...
    
    m_oThreadServices.OnLoadingCompleted -= OnLoadingComplete_AddFile;
    

    删除尚未添加(或已删除)的处理程序是无操作的,因此您可以在添加之前删除“其他”处理程序:这将确保最多有一个处理程序。

    【讨论】:

    • 感谢您的建议。我现在可以看到我可以声明两个单独的函数并根据需要删除。但是,我已经实现了其他答案之一并对其进行了标记。但我会给你一票。 :)
    【解决方案3】:

    所以基本上 += 是在您的事件上调用 Combine 的语法糖。委托存储在调用列表中,触发事件时的默认行为是调用列表中的每个委托按照添加的顺序被调用。这就是为什么您不能简单地将 OnLoadingCompleted 设置为带有 = 符号的一个委托 - 一个事件存储一个委托列表,而不是一个。

    您可以使用 -= 删除委托(调用 Remove 的语法糖)。也许您想在某处正式声明前一个委托,而不是将其作为 lambda 传递。这将让您在完成后将其删除。

    【讨论】:

    • 这实际上不是你不能分配给它的原因。事实上,您可以从定义它的类中分配给一个事件。您不能从该类的定义之外分配给事件,因为事件的概念是一个消费者可以影响该事件的另一个消费者。 按设计禁止
    • @Servy 我所解释的只是他为什么需要使用 +=。也许我的回答不清楚或者我误解了你?处理程序存储在列表中。
    • @bodangly 我明白你在说什么。这将是一种方法(以前声明事件函数)。我将为您的贡献投票,但我接受了其他答案之一。谢谢。
    【解决方案4】:

    没有直接的方法可以从处理程序中删除匿名或未知事件。不过,您可以查看 MSDN 上的这个论坛帖子:https://social.msdn.microsoft.com/Forums/vstudio/en-US/45071852-3a61-4181-9a25-068a8698b8b6/how-do-i-determine-if-an-event-has-a-handler-already?forum=netfxbcl

    有一些关于使用反射从事件处理程序中删除委托的代码和讨论。

    最好能准确地理解你想要完成的事情。也许有更好的方法来获得您正在寻找的最终结果,而不是重新连接事件。

    删除已建立的事件代码以更改您要实现的代码的行为通常不是好的做法。它可能导致意想不到的后果和不稳定的行为。如果定义了事件代码,最好保留它并围绕它设计您的应用程序。

    另一方面,如果这是您添加的代码,或者在您的代码库中,您可以将其删除,前提是您已经进行了适当的研究以验证它的删除并且不会导致应用程序在其他地方中断。最好的方法是将事件代码放在命名函数中:

    public void MyEventCode(object sender, EventArgs args)
    {
       // Do event stuff..
    }
    

    然后您可以按名称删除事件:

    control.DoMyEvent -= MyEventCode;
    

    【讨论】:

    • 如果他不将其作为 lambda 传递,他应该能够使用 -= 取消订阅委托。
    • 但这不是他需要删除的第一个 lambda 吗?除非我误会了,否则他想清除自己的处理程序,这完全是犹太教。
    • 如果我先加载,然后再使用 mru,则两者都会调用加载完成。它实际上是同一个处理程序,但我必须在两个菜单事件中定义它,因为我不能将代码放入表单构造函数中。
    • 是的,我重新阅读了这个问题并更新了我的答案。事件代码需要包含在可以引用的函数中,否则将返回到反射。
    • 您是否可以修改事件代码以具有基于某些条件执行的条件块?这难道不比在您的活动中添加和删除代表更好吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-24
    • 2015-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-14
    • 1970-01-01
    相关资源
    最近更新 更多