【问题标题】:Event Driven Programming事件驱动编程
【发布时间】:2015-10-06 09:03:39
【问题描述】:

我一直在阅读 this MSDN articlethis question 以尝试了解 .NET 中的事件。不幸的是,它不适合我,我遇到了很多麻烦。我正在尝试将此技术集成到我的项目中,但收效甚微。

基本上,我有这个会阅读数字的课程。每当它遇到一个新号码时,我希望它触发一个名为 numberChanged 的​​事件。

所以,我设置了我的活动public event EventHandler numberChanged;。稍后,当它遇到一个与前一个不同的数字时,我会触发我的事件。

if(currentNumber != previousNumber){
     if(numberChanged != null){
          numberChanged(this, new EventArgs());
     }
}

但是我在“订阅”此活动时遇到了麻烦。如果我这样做 numberChanged += [something to do here] 它会出错,说 numberChanged 是一个事件而不是一个类型。

我的解释是否足够清楚,可以提供一些建议?非常感谢。

【问题讨论】:

  • 你应该像这样订阅事件:yourclassInstance.numberChanged+=()=>{};或 numberChanged+=functionSignature;
  • @HadiHassan 你能澄清一下 += 之后的内容吗?
  • “+=”后面的表达式需要是与事件声明相同类型的委托。这可以是已声明方法的名称,也可以是匿名方法(使用 ()=>{} 语法)。
  • 你能发布导致错误的确切代码吗?您的 [something to do here] 代码可能会导致问题。
  • 创建Minimal, Complete, and Verifiable Example 会让其他人更容易帮助您调试。

标签: c#


【解决方案1】:

有多种处理方式,最基本的就是创建一个函数:

public void MyNumberChangedHandler(object sender, EventArgs e)
{
    //Your code goes here that gets called when the number changes
}

然后您通过以下方式订阅(仅一次,通常在构造函数中):

numberChanged += MyNumberChangedHandler;

或者,您可以使用一种称为匿名 (lambda) 方法的方法,该方法也在您的构造函数中分配(通常):

numberChanged += (sender, e) => {
    //Your code here to handle the number changed event
};

为了扩展一点,在使用 lambda 方法时必须小心,因为您可以创建内存泄漏和僵尸对象。 .NET 内存垃圾收集器是一种标记和清除系统,可在对象不再使用时将其删除。这篇文章展示了删除 lambda 事件处理程序是多么困难:How to remove a lambda event handler

拥有一个活动的事件处理程序可以使您的对象保持活动状态即使它已被释放!这是一个创建僵尸对象的示例(不在 Fiddle 中运行,但您可以复制到自己的控制台应用程序)https://dotnetfiddle.net/EfNpZ5

打印出来:

我还活着 我还活着 我被处置了! 按任意键退出 我还活着 我还活着 我还活着。

【讨论】:

  • 关于以编程方式添加事件处理程序的主题时,与以下内容有什么区别:myButton.Click += new EventHandler(myButton_Click);myButton.Click += myButton_Click
  • @sab669 技术上没有。
  • @sab669 只是 C# 2.0 中引入的一种语法糖
  • IDisposable.Dispose方法用于释放非托管资源,与释放托管内存无关。 IDisposable.Dispose 和垃圾收集器之间的关系是,如果你有一个释放非托管资源的 Dispose 方法,你需要创建一个终结器以确保即使从未调用 Dispose 也释放非托管资源。这与泄漏事件处理程序无关。
  • @MartinLiversage 抱歉,我不确定我 100% 理解这一点。在我之前的工作中,我养成了使用AttachEvents() 方法的习惯,该方法将使用new EventHandler() 语法附加事件,然后我们将拥有一个DettachEvents() 方法,我们将从表单设计器的Dispose() 方法中调用它.从未了解为什么我们这样做。我的印象基本上是GC的工作。或多或少,你指的是什么?
【解决方案2】:

与 C# 编程世界中的其他一切一样,事件概念也遵循特定规则并具有自己的语法。措辞如下:

  • 定义为EventHandler 的事件实际上只是一种特殊方法(委托)签名 - public delegate void EventHandler(object sender, EventArgs e)[1] 的快捷方式。每当您在 C# 中使用签名时,您总是知道需要在正确的位置或作为参数编写什么,以便连接/调用某些对象/方法/等等。
  • 定义事件后,您需要订阅以便在发生任何事情时得到通知。订阅事件的语法是 +=。退订自然是 -=。 MSDN 说语法应该是object.event += eventHandler(或object.event += new EventHandler(eventHandler);
  • 所以在定义了一个事件(event Event SomeEvent;)之后,剩下的就是创建一个可以绑定到这个事件的方法。此方法必须具有与 EventHandler 相同的签名,因此它应该与 [1] 的签名匹配,并且可以类似于 private void numberChangedEventHandler(object sender, EventArgs eventArguments)

现在您知道需要在 += 右侧写什么了。

一个例子:

public class NumberSequence
{
    // numbers to be compared
    private readonly List<int> numbers = new List<int>();
    // used to generate a random collection
    private readonly Random random = new Random();
    // tell me if the previous and next number are different
    public event EventHandler DifferentNumbersEvent;

    public NumberSequence()
    {
        // fill the list with random numbers
        Enumerable.Range(1, 100).ToList().ForEach(number =>
        {
            numbers.Add(random.Next(1, 100));
        });
    }

    public List<int> Numbers { get { return numbers; } }

    public void TraverseList()
    {
        for (var i = 1; i < this.numbers.Count; i++)
        {
            if (this.numbers[i - 1] != this.numbers[i])
            {
                if (this.DifferentNumbersEvent != null)
                {
                    // whoever listens - inform him
                    this.DifferentNumbersEvent(this, EventArgs.Empty);
                }
            }
        }
    }
}

现在在使用类之前,定义事件处理程序,它将监听并在事件触发时被调用(再次措辞):

private void differentNumberEventHandler(Object sender, EventArgs eventArguments)
{
    Console.WriteLine("Different numbers...");
}

及用法:

var ns = new NumberSequence();
ns.DifferentNumbersEvent += differentNumberEventHandler;
ns.TraverseList();

对于这种表示法(lambda / 匿名方法 / ...),其他一切都只是 syntactic sugar,例如:

object.Event += (s, e) =&gt; { // code ... };object.Event += (Object sender, EventArgs eventArguments) =&gt; { // code ... }; 相同。你认得签名吗? - 与private void differentNumberEventHandler...相同。

通常我们需要通过事件传递信息,在这种情况下我们可能希望看到这两个数字。 C# 允许您使用自定义事件参数轻松完成此操作。只需创建一个继承 EventArgs 类的类并为应该传递的数据添加属性,在本例中为数字:

public class NumbersInfoEventArgs : EventArgs
{
    public int Number1 { get; set; }
    public int Number2 { get; set; }
}

然后在声明事件时指定它将传递NumbersInfoEventArgs 类型的数据(再次签名):

public event EventHandler<NumbersInfoEventArgs> DifferentNumbersEvent;
...
this.DifferentNumbersEvent(this, new NumbersInfoEventArgs
{
    Number1 = this.numbers[i - 1],
    Number2 = this.numbers[i]
});

最后但现在最重要的是,事件处理程序的签名应该与事件的签名匹配:

private void differentNumberEventHandler(Object sender, NumbersInfoEventArgs eventArguments)
{
    Console.WriteLine("Different numbers {0} - {1}", eventArguments.Number1, eventArguments.Number2);
}

瞧,输出是:

Different numbers 89 - 86
Different numbers 86 - 53
Different numbers 53 - 12
Different numbers 12 - 69

【讨论】:

  • 就最佳实践而言,任何扩展EventArgs 类的类都应包含单词“EventArgs”,例如,NumbersInfo 应称为NumbersInfoEventArgs。见msdn.microsoft.com/en-us/library/edzehd2t(v=vs.110).aspx
  • 感谢您的提示(有时在我如此欣喜若狂的情况下,我往往会忘记指南),我已经更新了我的代码。
【解决方案3】:

您可以通过这种方式订阅活动:

using System;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");
        var num = new Number();
        num.numberChanged +=(s,e) =>{
            Console.WriteLine("Value was changed to {0}",num.Value); // in the demo below you can find another implementation for this sample using custom events
        };
        num.Value=10;
        num.Value=100;
    }
}

public class Number{
    public event EventHandler numberChanged;
    private int _value=0;
    public int Value
    {
        get{
            return _value;
        }
        set{
            if(value!=_value){
                _value=value;
                if(numberChanged!=null)
                    numberChanged(this,null);
            }
        }
    }
}

解释:

由于 EventHandler 委托有 2 个参数 (sender, eventArgs),如 here 所述,您需要传递这些参数,我将它们传递为 se

另一种订阅此事件的方式如下:

var num = new Number();
num.numberChanged += NumberChanged_Event; // below is the delegate method

public void NumberChanged_Event(object sender,EventArgs e)
{
   // your code goes here
}

我更新了演示以与您自己的委托一起使用,以传递旧值和新值,这在许多情况下会有所帮助。

这里是一个工作的demo

【讨论】:

    猜你喜欢
    • 2011-04-23
    • 1970-01-01
    • 2014-01-03
    • 2011-06-26
    • 2011-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多