代码之家  ›  专栏  ›  技术社区  ›  Richard Walton

C事件处理(与Java相比)

  •  11
  • Richard Walton  · 技术社区  · 16 年前

    我现在很难理解和实现C中使用Delagates的事件。我习惯了Java的做事方式:

    1. 为包含许多方法定义的侦听器类型定义接口
    2. 如果我对侦听器中定义的所有事件不感兴趣,请为该接口定义适配器类,以使操作更简单。
    3. 在引发事件的类中定义add、remove和get[]方法
    4. 定义受保护的Fire方法,以执行通过添加的侦听器列表循环并调用正确方法的脏工作。

    我明白(而且喜欢!)-我知道我也可以用C来做同样的事情,但这似乎是一个新的(更好的?)系统已为C_准备就绪。在阅读了无数的教程解释了C中代表和事件的使用之后,我仍然无法真正理解发生了什么:S。


    简而言之,对于以下方法,我将如何在C中实现事件系统:

    void computerStarted(Computer computer);
    void computerStopped(Computer computer);
    void computerReset(Computer computer);
    void computerError(Computer computer, Exception error);
    

    上面的方法是从我曾经做过的一个Java应用程序中获取的,我试图把它移植到C语言中。

    非常感谢!

    9 回复  |  直到 16 年前
        1
  •  16
  •   Jon Skeet    16 年前

    您将创建四个事件和引发它们的方法,以及一个新的基于EventArgs的类来指示错误:

    public class ExceptionEventArgs : EventArgs
    {
        private readonly Exception error;
    
        public ExceptionEventArgs(Exception error)
        {
             this.error = error;
        }
    
        public Error
        {
             get { return error; }
        }
    }
    
    public class Computer
    {
        public event EventHandler Started = delegate{};
        public event EventHandler Stopped = delegate{};
        public event EventHandler Reset = delegate{};
        public event EventHandler<ExceptionEventArgs> Error = delegate{};
    
        protected void OnStarted()
        {
            Started(this, EventArgs.Empty);
        }
    
        protected void OnStopped()
        {
            Stopped(this, EventArgs.Empty);
        }
    
        protected void OnReset()
        {
            Reset(this, EventArgs.Empty);
        }
    
        protected void OnError(Exception e)
        {
            Error(this, new ExceptionEventArgs(e));
        }
    }
    

    然后,类将使用方法或匿名函数订阅事件:

    someComputer.Started += StartEventHandler; // A method
    someComputer.Stopped += delegate(object o, EventArgs e)
    { 
        Console.WriteLine("{0} has started", o);
    };
    someComputer.Reset += (o, e) => Console.WriteLine("{0} has been reset");
    

    关于以上几点需要注意:

    • onxxx方法受到保护,以便派生类可以引发事件。这并不总是必要的-做你认为合适的。
    • 这个 delegate{} 每个事件声明上的片段只是一个避免进行空检查的技巧。它为每个事件订阅一个非操作事件处理程序
    • 事件声明是 类场事件 . 实际上正在创建的都是一个变量 一个事件。在类内部,您可以看到变量;在类外部,您可以看到事件。

    看到我 events/delegates 有关事件的更多详细信息。

        2
  •  5
  •   jop    16 年前

    您必须为此定义一个委托

    public delegate void ComputerEvent(object sender, ComputerEventArgs e);
    

    ComputerEventArgs的定义如下:

    public class ComputerEventArgs : EventArgs
    {
        // TODO wrap in properties
        public Computer computer;
        public Exception error;
    
        public ComputerEventArgs(Computer aComputer, Exception anError)
        {
            computer = aComputer;
            error = anError;
        }
    
        public ComputerEventArgs(Computer aComputer) : this(aComputer, null)
        {
        }
    }
    

    触发事件的类将具有以下属性:

    public YourClass
    {
        ...
        public event ComputerEvent ComputerStarted;
        public event ComputerEvent ComputerStopped;
        public event ComputerEvent ComputerReset;
        public event ComputerEvent ComputerError;
        ...
    }
    

    这是为事件分配处理程序的方式:

    YourClass obj = new YourClass();
    obj.ComputerStarted += new ComputerEvent(your_computer_started_handler);
    

    您的处理程序是:

    private void ComputerStartedEventHandler(object sender, ComputerEventArgs e)
    {
       // do your thing.
    }
    
        3
  •  4
  •   Franci Penov    16 年前

    主要区别在于,在C中,事件不是基于接口的。相反,事件发布者声明了可以将其视为函数指针的委托(尽管不完全相同:-)。然后,订阅服务器将事件原型作为常规方法实现,并将委托的新实例添加到发布服务器的事件处理程序链中。了解更多信息 delegates events .

    您还可以阅读C比较vs. Java事件的简短比较。 here .

        4
  •  2
  •   Jeffrey L Whitledge    16 年前

    首先,在.NET中有一个标准方法签名,通常用于事件。语言允许在事件中使用任何类型的方法签名,有些专家认为该约定有缺陷(我大多数同意),但它就是这样,我将在本例中遵循它。

    1. 创建一个类,该类将包含事件的参数(从eventargs派生)。
    public class ComputerEventArgs : EventArgs 
    {
      Computer computer; 
      // constructor, properties, etc.
    }
    
    1. 在要激发事件的类上创建公共事件。
        class ComputerEventGenerator  // I picked a terrible name BTW.
        {
          public event EventHandler<ComputerEventArgs> ComputerStarted;
          public event EventHandler<ComputerEventArgs> ComputerStopped;
          public event EventHandler<ComputerEventArgs> ComputerReset;
        ...
        }
    
    1. 呼叫事件。
        class ComputerEventGenerator
        {
        ...
          private void OnComputerStarted(Computer computer) 
          {
            EventHandler<ComputerEventArgs> temp = ComputerStarted;
            if (temp != null) temp(this, new ComputerEventArgs(computer)); // replace "this" with null if the event is static
          }
         }
    
    1. 附加事件的处理程序。
        void OnLoad()
        {
          ComputerEventGenerator computerEventGenerator = new ComputerEventGenerator();
          computerEventGenerator.ComputerStarted += new  EventHandler<ComputerEventArgs>(ComputerEventGenerator_ComputerStarted);
        }
    
    1. 创建刚刚附加的处理程序(主要是通过按vs中的tab键)。
        private void ComputerEventGenerator_ComputerStarted(object sender, ComputerEventArgs args)
        {
          if (args.Computer.Name == "HAL9000")
             ShutItDownNow(args.Computer);
        }
    
    1. 完成后不要忘记分离处理程序。(忘记这样做是C中最大的内存泄漏源)
        void OnClose()
        {
          ComputerEventGenerator.ComputerStarted -= ComputerEventGenerator_ComputerStarted;
        }
    

    就这样!

    编辑:我真的不明白为什么我的数字点都显示为“1”。我讨厌电脑。

        5
  •  1
  •   Steven A. Lowe    16 年前

    做你想做的事有几种方法。这个 最直接 方法是为宿主类中的每个事件定义委托,例如

    public delegate void ComputerStartedDelegate(Computer computer);
    protected event ComputerStartedDelegate ComputerStarted;
    public void OnComputerStarted(Computer computer)
    {
        if (ComputerStarted != null)
        {
            ComputerStarted.Invoke(computer);
        }
    }
    protected void someMethod()
    {
        //...
        computer.Started = true;  //or whatever
        OnComputerStarted(computer);
        //...
    }
    

    任何对象都可以通过以下方式“监听”此事件:

    Computer comp = new Computer();
    comp.ComputerStarted += new ComputerStartedDelegate(
        this.ComputerStartedHandler);
    
    protected void ComputerStartedHandler(Computer computer)
    {
        //do something
    }
    

    这样做的“推荐的标准方法”是定义EventArgs的子类来保存计算机(以及旧/新状态和异常)值,将4个委托减少到一个。在这种情况下,这将是一个更清洁的解决方案,特别是在以后扩展时,对计算机状态使用枚举。但是基本的技术还是一样的:

    • 委托为事件处理程序/侦听器定义签名/接口
    • 事件数据成员是“侦听器”列表

    使用-=语法而不是+=

        6
  •  1
  •   Andrew Kennan    16 年前

    在C中,事件是代表。它们的行为类似于C/C++中的函数指针,但它们是从Stase.委派派生的实际类。

    在这种情况下,创建一个自定义的EventArgs类来传递计算机对象。

    public class ComputerEventArgs : EventArgs
    {
      private Computer _computer;
    
      public ComputerEventArgs(Computer computer) {
        _computer = computer;
      }
    
      public Computer Computer { get { return _computer; } }
    }
    

    然后从生产者那里公开事件:

    public class ComputerEventProducer
    {
      public event EventHandler<ComputerEventArgs> Started;
      public event EventHandler<ComputerEventArgs> Stopped;
      public event EventHandler<ComputerEventArgs> Reset;
      public event EventHandler<ComputerEventArgs> Error;
    
      /*
      // Invokes the Started event */
      private void OnStarted(Computer computer) {
        if( Started != null ) {
          Started(this, new ComputerEventArgs(computer));
        }
      }
    
      // Add OnStopped, OnReset and OnError
    
    }
    

    然后事件的使用者将处理程序函数绑定到使用者上的每个事件。

    public class ComputerEventConsumer
    {
      public void ComputerEventConsumer(ComputerEventProducer producer) {
        producer.Started += new EventHandler<ComputerEventArgs>(ComputerStarted);
        // Add other event handlers
      }
    
      private void ComputerStarted(object sender, ComputerEventArgs e) {
      }
    }
    

    当ComputerEventProducer调用OnStarted时,将调用Started事件,该事件反过来将调用ComputerEventConsumer.ComputerStarted方法。

        7
  •  1
  •   loudej    16 年前

    委托声明了一个函数签名,当它用作类上的事件时,它还充当已登记调用目标的集合。事件的+=和-=语法用于将目标添加到列表中。

    鉴于以下委托用作事件:

    // arguments for events
    public class ComputerEventArgs : EventArgs
    {
        public Computer Computer { get; set; }
    }
    
    public class ComputerErrorEventArgs : ComputerEventArgs
    {
        public Exception Error  { get; set; }
    }
    
    // delegates for events
    public delegate void ComputerEventHandler(object sender, ComputerEventArgs e);
    
    public delegate void ComputerErrorEventHandler(object sender, ComputerErrorEventArgs e);
    
    // component that raises events
    public class Thing
    {
        public event ComputerEventHandler Started;
        public event ComputerEventHandler Stopped;
        public event ComputerEventHandler Reset;
        public event ComputerErrorEventHandler Error;
    }
    

    您可以通过以下方式订阅这些事件:

    class Program
    {
        static void Main(string[] args)
        {
            var thing = new Thing();
            thing.Started += thing_Started;
        }
    
        static void thing_Started(object sender, ComputerEventArgs e)
        {
            throw new NotImplementedException();
        }
    }
    

    尽管参数可以是任何东西,但对象发送者和事件参数e是一个非常一致使用的约定。启动的+=事件将首先创建指向目标方法的委托实例,然后将其添加到事件中。

    在组件本身上,您通常会添加方法来激发事件:

    public class Thing
    {
        public event ComputerEventHandler Started;
    
        public void OnStarted(Computer computer)
        {
            if (Started != null)
                Started(this, new ComputerEventArgs {Computer = computer});
        }
    }
    

    如果没有向事件中添加委托,则必须测试是否为空。但是,当您进行方法调用时,将调用已添加的所有委托。这就是事件返回类型为void(没有单个返回值)的原因,因此为了反馈信息,您将拥有事件处理程序将更改的EventArgs的属性。

    另一种改进是使用通用的EventHandler委托,而不是为每种类型的参数声明具体的委托。

    public class Thing
    {
        public event EventHandler<ComputerEventArgs> Started;
        public event EventHandler<ComputerEventArgs> Stopped;
        public event EventHandler<ComputerEventArgs> Reset;
        public event EventHandler<ComputerErrorEventArgs> Error;
    }
    
        8
  •  0
  •   Richard Walton    16 年前

    非常感谢大家的回答!最后,我开始明白发生了什么。只有一件事;如果每个事件的参数数目/类型不同,我需要创建一个不同的::EventArgs类来处理它:

    public void computerStarted(Computer computer);
    public void computerStopped(Computer computer);
    public void computerReset(Computer computer);
    public void breakPointHit(Computer computer, int breakpoint);
    public void computerError(Computer computer, Exception exception);
    

    这需要三个类别来处理这些事件!?(两个自定义,一个使用默认的eventargs.empty类)

    干杯!

        9
  •  0
  •   Richard Walton    16 年前

    好的,最后澄清!:所以这几乎是我在实现这些事件时所能做的最明智的代码了?

       public class Computer {
    
            public event EventHandler Started;
    
            public event EventHandler Stopped;
    
            public event EventHandler Reset;
    
            public event EventHandler<BreakPointEvent> BreakPointHit;
    
            public event EventHandler<ExceptionEvent> Error;
    
            public Computer() {
                Started = delegate { };
                Stopped = delegate { };
                Reset = delegate { };
                BreakPointHit = delegate { };
                Error = delegate { };
            }
    
            protected void OnStarted() {
                Started(this, EventArgs.Empty);
            }
    
            protected void OnStopped() {
                Stopped(this, EventArgs.Empty);
            }
    
            protected void OnReset() {
                Reset(this, EventArgs.Empty);
            }
    
            protected void OnBreakPointHit(int breakPoint) {
                BreakPointHit(this, new BreakPointEvent(breakPoint));
            }
    
            protected void OnError(System.Exception exception) {
                Error(this, new ExceptionEvent(exception));
            }
        }
    }