如何在基类中触发事件,子类是否必须订阅它?

问题描述 投票:0回答:1

有一些关于这个主题的好帖子,包括this。基本思想是,你的基类中有一个事件,但是当你去触发它时,你发现自己无法这样做。

public class Persist
{
    public event EventHandler? Modified;
}
public class Settings : Persist
{
    public string DatabasePath
    {
        get => _databasePath;
        set
        {
            if (!Equals(_databasePath, value))
            {
                _databasePath = value;

                // N O T    A L L O W E D
                Modified?.Invoke(this, EventArgs.Empty);
            }
        }
    }
    string _databasePath = string.Empty;
}

代码不会像这样编译。


所以,问题的第一部分是如何提出。这是第二部分:

一旦事件确实被引发(这可能是因为基类自己决定是时候触发它了),子类如何侦听该事件并通过执行一些特定于类的代码来响应?

例如,当您使用设计器将事件添加到主

Form
时,您会在Designer.cs文件中获得该事件...

private void InitializeComponent()
{
    Load += MainForm_Load;
    .
    .
    .
}

...以及 .cs 文件中的内容:

private void MainForm_Load(object sender, EventArgs e)
{
}

但是为什么

MainForm
首先必须订阅自己的事件?

c# winforms code-snippets
1个回答
0
投票

差不多 20 年前(我知道,对吧?...),我突然想到编写一个 代码片段 来实现允许调用基类事件的规范事件模式。今天,大约第 1000 次使用它,我想知道分享它是否也可以给其他人一些额外的空闲时间。

<CodeSnippet Format="1.0.0">  
    <Header>   
        <Title>Event Template</Title>
        <Description>Event Template</Description>  
        <Author>IVSoftware,LLC</Author>   
        <Shortcut>eventiv</Shortcut> 
    </Header>  
    <Snippet>
        <Declarations>
            <Literal>
                <ID>fooey</ID>
                <ToolTip>Replace with an event name.</ToolTip>
                <Default>Name</Default>
            </Literal>
        </Declarations>
        <Code Language="CSharp">    
            <![CDATA[
                    public event $fooey$EventHandler $fooey$;
                    protected virtual void On$fooey$($fooey$EventArgs e)
                    {
                        $fooey$?.Invoke(this, e);
                    } 
                    public delegate void $fooey$EventHandler(object sender, $fooey$EventArgs e);
                    public class $fooey$EventArgs : EventArgs
                    {
                        public $fooey$EventArgs($end$)
                        {
                        }
                    }
                    ]]>  
        </Code>  
    </Snippet>  
</CodeSnippet>

现在,输入快捷方式会弹出模板...

...您可以在

Name
占位符的第一个实例中输入(例如)“Modified”。


最终结果

我的习惯是将

EventArgs
类和
delegate
移到类之外(无论哪种方式都可以)以生成此生成的代码。

public class Persist
{
    public event ModifiedEventHandler Modified;
    protected virtual void OnModified(ModifiedEventArgs e)
    {
        Modified?.Invoke(this, e);
    }
}
public delegate void ModifiedEventHandler(object sender, ModifiedEventArgs e);
public class ModifiedEventArgs : EventArgs
{
    public ModifiedEventArgs()
    {
    }
}

触发事件

ModifiedEventArgs
类提供在此上下文中有用的属性后,您现在可以开火了。

public class Settings : Persist
{
    public string DatabasePath
    {
        get => _databasePath;
        set
        {
            if (!Equals(_databasePath, value))
            {
                _databasePath = value;
                OnModified(new ModifiedEventArgs());
            }
        }
    }
    string _databasePath = string.Empty;
}

子类必须订阅吗?

好的,现在我想在我的子类中做一些事情来响应被触发的事件。在 Winforms 中,你会看到很多这样的情况。例如,您在表单设计器中创建一个事件,它会生成如下内容:

private void MainForm_Load(object sender, EventArgs e)
{
    .
    .
    .
}

如果您想知道,为什么

Form
需要订阅自己的事件?答案在于这样一个事实:这使得设计者可以轻松生成代码(以它自己丑陋的方式)来建立连接点。

作为前向引用,它在功能上相当于

override
OnLoad
方法,这使得能够在调用订阅的事件处理程序之前或之后添加特定于类的功能。

protected override void OnLoad(EventArgs e)
{
    // Add functionality to take place BEFORE the event is fired to event handlers.
    base.OnLoad(e); // <- This is what fires the Load event.
    // Add functionality to take place AFTER the event is fired to event handlers.
}

事件“幕后”是什么样子

查看完成的自定义类,很容易理解为什么会出现这种情况:

public class Persist
{
    public event ModifiedEventHandler Modified;
    protected virtual void OnModified(ModifiedEventArgs e)
    {
        Modified?.Invoke(this, e);
    }
}
public class Settings : Persist
{
    public string DatabasePath
    {
        get => _databasePath;
        set
        {
            if (!Equals(_databasePath, value))
            {
                _databasePath = value;
                OnModified(new ModifiedEventArgs());
            }
        }
    }
    string _databasePath = string.Empty;

    // Optional override. The alternative would be to
    // have Settings subscribe to its own event:
    //      Modified += Settings_Modified;
    protected override void OnModified(ModifiedEventArgs e)
    {            
        // Add functionality to take place BEFORE the event is fired to event handlers.
        base.OnModified(e); // <- This is what fires the Load event.
        // Add functionality to take place AFTER the event is fired to event handlers.
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.