代码之家  ›  专栏  ›  技术社区  ›  Cheng Chen

将事件视为对象

  •  0
  • Cheng Chen  · 技术社区  · 15 年前

    还不够吗?这里我举一个(也许不好的)例子。

    public class Program
    {
       public event EventHandler OnStart;
       public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts");
    
       public class MyCSharpProgram
       {
          public string Name { get; set; }
          public event EventHandler OnStart;
          public void Start()
          {
              OnStart(this, EventArgs.Empty);
          }
       }
    
       static void Main(string[] args)
       {
          MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" };
          cs.OnStart += LogOnStart;  //can compile
          //RegisterLogger(cs.OnStart);   // Line of trouble
          cs.Start();   // it prints "start" (of course it will :D)
    
          Program p = new Program();
          RegisterLogger(p.OnStart);   //can compile
          p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime
          Console.Read();
        }
    
        static void RegisterLogger(EventHandler ev)
        {
          ev += LogOnStart;
        }
    }
    

    寄存器记录器(cs.OnStart公司)导致编译错误,因为“事件XXX只能出现在+=或-=blabla的左侧”。但是为什么RegisterLogger(p.OnStart)可以呢?同时,虽然我注册了p.OnStart,但它也会抛出一个NullReferenceException,似乎p.OnStart并没有“真正”传递给一个方法。

    4 回复  |  直到 15 年前
        1
  •  1
  •   Community CDub    8 年前

    “事件XXX只能出现在+=或-=blabla的左侧”

    这实际上是因为C# += -= 操作员。这样声明类就可以完全控制调用事件的时间。客户端代码只能在中有发言权 当他们被召唤的时候。

    你的代码 RegisterLogger(p.OnStart) compiles是从 Program Program.OnStart 事件已声明。

    你的代码 RegisterLogger(cs.OnStart) compile是从 同学们,但是 MyCSharpProgram.OnStart 事件(显然)是在 MyCSharpProgram 班级。

    作为 Chris Taylor 指出,你得到 NullReferenceException 在线上 p.OnStart(p, EventArgs.Empty); RegisterLogger 正如你所拥有的 将一个新值赋给一个局部变量,当该局部变量作为参数传入时,对该局部变量所赋的对象没有影响 . 为了更好地理解这一点,请考虑以下代码:

    static void IncrementValue(int value)
    {
        value += 1;
    }
    
    int i = 0;
    IncrementValue(i);
    
    // Prints '0', because IncrementValue had no effect on i --
    // a new value was assigned to the COPY of i that was passed in
    Console.WriteLine(i);
    

    就像一种需要 int 作为参数并为其指定新值只影响复制到其堆栈中的局部变量,该方法采用 EventHandler 只影响 它的 局部变量(在赋值中)。

        2
  •  1
  •   Chris Taylor    15 年前

    对RegisterLogger进行以下更改,声明 ev

    static void RegisterLogger(ref EventHandler ev) 
    { 
      ev += LogOnStart; 
    }
    

    然后您的调用点在调用方法时还需要使用'ref'关键字,如下所示

    RegisterLogger(ref p.OnStart);
    
        3
  •  1
  •   Merlyn Morgan-Graham    15 年前

    寄存器记录器(cs.OnStart公司);

    ... 事件处理程序和传递它的方法在不同的类中。C#非常严格地处理事件,只允许事件出现的类执行除添加处理程序以外的任何操作(包括将它传递给函数或调用它)。

    例如,它也不会编译(因为它在不同的类中):

    cs.OnStart(cs, EventArgs.Empty);
    

    至于不能以这种方式将事件处理程序传递给函数,我不确定。我猜事件就像值类型一样运行。通过ref传递它可以解决您的问题,不过:

    static void RegisterLogger(ref EventHandler ev)
    {
        ev += LogOnStart;
    }
    
        4
  •  0
  •   Jeff Mercado    15 年前

    null . 这是故意的。以下是框架中使用的典型模式:

    public class MyCSharpProgram
    {
        // ...
    
        // define the event
        public event EventHandler SomeEvent;
    
        // add a mechanism to "raise" the event
        protected virtual void OnSomeEvent()
        {
            // SomeEvent is a "variable" to a EventHandler
            if (SomeEvent != null)
                SomeEvent(this, EventArgs.Empty);
        }
    }
    
    // etc...
    

    现在,如果您必须坚持在类外部公开委托,请不要将其定义为事件。然后可以将其视为任何其他字段或属性。

    我修改了您的示例代码以说明:

    public class Program
    {
        public EventHandler OnStart;
        public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts");
    
        public class MyCSharpProgram
        {
            public string Name { get; set; }
    
            // just a field to an EventHandler
            public EventHandler OnStart = (s, e) => { /* do nothing */ }; // needs to be initialized to use "+=", "-=" or suppress null-checks
            public void Start()
            {
                // always check if non-null
                if (OnStart != null)
                    OnStart(this, EventArgs.Empty);
            }
        }
    
        static void Main(string[] args)
        {
            MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" };
            cs.OnStart += LogOnStart;  //can compile
            RegisterLogger(cs.OnStart);   // should work now
            cs.Start();   // it prints "start" (of course it will :D)
    
            Program p = new Program();
            RegisterLogger(p.OnStart);   //can compile
            p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime
            Console.Read();
        }
    
        static void RegisterLogger(EventHandler ev)
        {
            // Program.OnStart not initialized so ev is null
            if (ev != null) //null-check just in case
                ev += LogOnStart;
        }
    }