【问题标题】:Raise an event of a class from a different class in C#从 C# 中的不同类引发类的事件
【发布时间】:2010-12-07 15:28:12
【问题描述】:

我有一个类,EventContainer.cs,它包含一个事件,比如说:

public event EventHandler AfterSearch;

我还有另一个课程,EventRaiser.cs。如何从此类中引发(而不是处理)上述事件?

引发的事件将依次调用 EventContainer 类中的事件处理程序。像这样的东西(这显然是不正确的):

EventContainer obj = new EventContainer(); 
RaiseEvent(obj.AfterSearch);

【问题讨论】:

  • 这篇旧帖子提出了一个问题,可以使用 TinyMessenger 等事件中心轻松规避。

标签: c# events


【解决方案1】:

这是不可能的,事件只能从班级内部发起。如果你能做到这一点,它将破坏事件的目的(能够从班级内部提升状态变化)。我认为您误解了事件的功能 - 一个事件是在一个类中定义的,其他人可以通过这样做来订阅它

obj.AfterSearch += handler;(其中 handler 是根据AfterSearch 签名的方法)。一个可以从外部订阅事件就好了,但它只能从定义它的类内部升起。

【讨论】:

  • 感谢 Femaref 提供的内幕消息。将尝试以其他方式实现。
  • @Femaref:这是可能,在下面试试我的答案。
  • 有实用性吗?不,它没有,你的第一行说,这是一个 hack,如果我在源代码的某个地方找到它,我会慢慢关闭窗口并平静地工作,永远不会再被看到。
  • @Femaref:我没有说它是实用性,我说“有可能”,因为你说“这不可能,事件只能从班级内部发起” - 但它不是真的。
  • @Femaref 顺便说一句......当这样做时:'combo.selectedvalue = "whatever"'。然后不会引发组合上的 SelectionChanged 事件。对我来说似乎是一个错误。在这种情况下,从组合类之外的地方引发一个事件会非常方便。 (对于迟到的帖子感到抱歉)
【解决方案2】:

这是可能的,但使用巧妙的技巧。

灵感来自http://netpl.blogspot.com/2010/10/is-net-type-safe.html

如果你不相信,试试这个代码。

using System;
using System.Runtime.InteropServices;

namespace Overlapping
{
    [StructLayout(LayoutKind.Explicit)]
    public class OverlapEvents
    {
        [FieldOffset(0)]
        public Foo Source;

        [FieldOffset(0)]
        public OtherFoo Target;
    }

    public class Foo
    {
        public event EventHandler Clicked;

        public override string ToString()
        {
            return "Hello Foo";
        }

        public void Click()
        {
            InvokeClicked(EventArgs.Empty);
        }

        private void InvokeClicked(EventArgs e)
        {
            var handler = Clicked;
            if (handler != null)
                handler(this, e);
        }
    }

    public class OtherFoo
    {
        public event EventHandler Clicked;

        public override string ToString()
        {
            return "Hello OtherFoo";
        }

        public void Click2()
        {
            InvokeClicked(EventArgs.Empty);
        }

        private void InvokeClicked(EventArgs e)
        {
            var handler = Clicked;
            if (handler != null)
                handler(this, e);
        }

        public void Clean()
        {
            Clicked = null;
        }
    }

    class Test
    {
        public static void Test3()
        {
            var a = new Foo();
            a.Clicked += AClicked;
            a.Click();
            var o = new OverlapEvents { Source = a };
            o.Target.Click2();
            o.Target.Clean();

            o.Target.Click2();
            a.Click();
        }

        static void AClicked(object sender, EventArgs e)
        {
            Console.WriteLine(sender.ToString());
        }
    }
}

【讨论】:

  • 真的很棒!从来没有见过一个类中的“this”是它所使用的类的类型以外的任何其他类型。
  • 这没有回答问题。您不是从类外引发事件,而是在调用一个从类内引发事件的函数,这就是上面的答案所说的。如果你只需要通知另一个类,你应该使用中介者模式(这个解决方案松散地基于它)。
【解决方案3】:

您可以在要触发事件的类上编写一个公共方法,并在调用该事件时触发该事件。然后,您可以从您班级的任何用户调用此方法。

当然,这会破坏封装并且是糟糕的设计。

【讨论】:

  • 我绝对同意你的看法,这将是一个糟糕的设计,但我经常使用的模式是有一个名为 GlobalEventspublic static class,你可以调用它来传递 任何类都可以听。当前执行此操作的唯一方法是使用System.Action。当我想要命名参数时,问题就出现了。目前有一个 XML 摘要标记,您可以使用参数填充它,但它仍然不那么可读,并且在大多数 IDE 的自动摘要功能中不原生支持。
【解决方案4】:

您似乎正在使用Delegate pattern。在这种情况下,AfterSearch 事件应该在 EventRaiser 类上定义,EventContainer 类应该消费该事件:

在 EventRaiser.cs 中

public event EventHandler BeforeSearch;
public event EventHandler AfterSearch;

public void ExecuteSearch(...)
{
    if (this.BeforeSearch != null)
      this.BeforeSearch();

    // Do search

    if (this.AfterSearch != null)
      this.AfterSearch();
}

在 EventContainer.cs 中

public EventContainer(...)
{
    EventRaiser er = new EventRaiser();

    er.AfterSearch += this.OnAfterSearch;
}

public void OnAfterSearch()
{
   // Handle AfterSearch event
}

【讨论】:

    【解决方案5】:

    我也偶然发现了这个问题,因为我正在尝试从外部调用 PropertyChanged 事件。因此,您不必在每个类中都实现所有内容。 halorty 的解决方案无法使用接口。

    我找到了一个使用重度反射的解决方案。它肯定很慢,并且违反了只能从类内部调用事件的原则。但是找到这个问题的通用解决方案很有趣....

    之所以有效,是因为每个事件都是被调用的调用方法的列表。 所以我们可以获取调用列表,并通过我们自己的方式调用附加到该事件的每个侦听器。

    给你....

    class Program
    {
      static void Main(string[] args)
      {
        var instance = new TestPropertyChanged();
        instance.PropertyChanged += PropertyChanged;
    
        instance.RaiseEvent(nameof(INotifyPropertyChanged.PropertyChanged), new PropertyChangedEventArgs("Hi There from anywhere"));
        Console.ReadLine();
      }
    
      private static void PropertyChanged(object sender, PropertyChangedEventArgs e)
      {
        Console.WriteLine(e.PropertyName);
      }
    }
    
    public static class PropertyRaiser
    {
      private static readonly BindingFlags staticFlags = BindingFlags.Instance | BindingFlags.NonPublic;
    
      public static void RaiseEvent(this object instance, string eventName, EventArgs e)
      {
        var type = instance.GetType();
        var eventField = type.GetField(eventName, staticFlags);
        if (eventField == null)
          throw new Exception($"Event with name {eventName} could not be found.");
        var multicastDelegate = eventField.GetValue(instance) as MulticastDelegate;
        if (multicastDelegate == null)
          return;
    
        var invocationList = multicastDelegate.GetInvocationList();
    
        foreach (var invocationMethod in invocationList)
          invocationMethod.DynamicInvoke(new[] {instance, e});
      }
    }
    
    public class TestPropertyChanged : INotifyPropertyChanged
    {
      public event PropertyChangedEventHandler PropertyChanged;
    }
    

    【讨论】:

      【解决方案6】:

      有很好的方法来做到这一点。 C# 中的每个事件都有一个委托,该委托指定该事件的方法符号。在外部类中使用事件委托的类型定义一个字段。在外部类的构造函数中获取该字段的引用并保存。在您的事件的主类中,将事件的引用发送给外部类的委托。现在您可以轻松地在外部类中调用委托。

      public delegate void MyEventHandler(object Sender, EventArgs Args);
      
      public class MyMain
      {
           public event MyEventHandler MyEvent;
           ...
           new MyExternal(this.MyEvent);
           ...
      }
      
      public MyExternal
      {
           private MyEventHandler MyEvent;
           public MyExternal(MyEventHandler MyEvent)
           {
                 this.MyEvent = MyEvent;
           }
           ...
           this.MyEvent(..., ...);
           ...
      }
      

      【讨论】:

      • 我无法确定这个答案的头或尾; ... 停止工作并隐藏太多,MyEvent 变量名的重用无济于事,我认为它不能解决问题。
      • @webturner:他在一个类中声明一个事件,然后在实例化期间将其传递给另一个类并从新类调用事件。可以肯定的是,这很骇人听闻,但我相信它会起作用。
      • 这行不通。事件处理程序被复制,并且在复制之后订阅的所有订阅者都不会收到通知。
      • 在这里链接@Johannes 更正确的答案:stackoverflow.com/a/37933566/661933。演示:dotnetfiddle.net/sCrZzu
      【解决方案7】:

      同意 Femaref -- 并注意这是代表和事件之间的一个重要区别(例如,请参阅 this blog entry 以很好地讨论这一点和其他区别)。

      根据您想要实现的目标,您可能会更好地使用委托。

      【讨论】:

        【解决方案8】:

        不是一个好的编程,但如果你想这样做,你可以做这样的事情

        class Program
        {
            static void Main(string[] args)
            {
        
                Extension ext = new Extension();
                ext.MyEvent += ext_MyEvent;
                ext.Dosomething();
            }
        
            static void ext_MyEvent(int num)
            {
                Console.WriteLine(num);
            }
        }
        
        
        public class Extension
        {
            public delegate void MyEventHandler(int num);
            public event MyEventHandler MyEvent;
        
            public void Dosomething()
            {
                int no = 0;
                while(true){
                    if(MyEvent!=null){
                        MyEvent(++no);
                    }
                }
            }
        }
        

        【讨论】:

        • 虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。
        【解决方案9】:

        我也有类似的困惑,老实说,这里的答案令人困惑。虽然有几个人暗示了我后来发现可行的解决方案。

        我的解决方案是阅读书籍并更加熟悉委托和事件处理程序。 虽然我已经使用了很多年,但我从未对它们非常熟悉。 http://www.codeproject.com/Articles/20550/C-Event-Implementation-Fundamentals-Best-Practices 给出了我读过的委托和事件处理程序的最佳解释,并清楚地解释了一个类可以成为事件的发布者并让其他类使用它们。 这篇文章:http://www.codeproject.com/Articles/12285/Implementing-an-event-which-supports-only-a-single 讨论了如何将事件单播到一个处理程序,因为根据定义委托是多播的。一个委托继承了system.MulticastDelegate,其中大部分系统委托都是Multicast。 我发现多播意味着任何具有相同签名的事件处理程序都会收到引发的事件。当我单步执行代码并看到我的事件似乎被错误地发送给我无意获取此事件的处理程序时,多播行为让我有些不眠之夜。两篇文章都解释了这种行为。 第二篇文章向您展示了一种方式,而第一篇文章向您展示了另一种方式,即使委托和签名紧密键入。 我个人认为强类型可以防止那些很难找到的愚蠢错误。所以我会投票给第一篇文章,即使我得到了第二篇文章的代码。我只是好奇而已。 :-)

        我也很好奇我是否可以让#2 文章代码表现得像我解释上面的原始问题一样。无论您选择哪种方法,或者我是否也误解了原始问题,我的真实信息是,我仍然认为您会像我一样从阅读第一篇文章中受益,特别是如果此页面上的问题或答案让您感到困惑。如果您遇到多播噩梦并需要快速解决方案,那么第 2 条可能会对您有所帮助。

        我开始玩第二篇文章的 eventRaiser 类。我做了一个简单的windows窗体项目。 我将第二篇文章类 EventRaiser.cs 添加到我的项目中。 在主窗体的代码中,我将顶部的 EventRaiser 类的引用定义为

        private EventRaiser eventRaiser = new EventRaiser();
        

        我在主窗体代码中添加了一个方法,我想在事件触发时调用它

        protected void MainResponse( object sender, EventArgs eArgs )
        {            
            MessageBox.Show("got to MainResponse");
        }
        

        然后在主窗体的构造函数中添加了事件赋值:

        eventRaiser.OnRaiseEvent += new EventHandler(MainResponse);`
        

        然后,由于目前缺乏创造性,我创建了一个由我的主窗体“SimpleClass”实例化的类。

        然后我添加了一个按钮并在按钮的点击事件中 我实例化了我想从中引发事件的 SimpleClass 代码:

            private void button1_Click( object sender, EventArgs e )
           {            
               SimpleClass sc = new SimpleClass(eventRaiser);
           }
        

        请注意我传递给 SimpleClass.cs 的“eventRaiser”实例。这是在主窗体代码中定义和实例化的。

        在 SimpleClass 中:

        using System.Windows.Forms;
        using SinglecastEvent; // see SingleCastEvent Project for info or http://www.codeproject.com/Articles/12285/Implementing-an-event-which-supports-only-a-single
        
            namespace GenericTest
            {
        
                public class SimpleClass
                {
        
                    private EventRaiser eventRaiser = new EventRaiser();
        
                    public SimpleClass( EventRaiser ev )
                    {
                        eventRaiser = ev;
                        simpleMethod();
        
                    }
                    private void simpleMethod()
                    {
        
                        MessageBox.Show("in FileWatcher.simple() about to raise the event");
                        eventRaiser.RaiseEvent();
                    }
                }
            }
        

        我称为 SimpleMethod 的私有方法的唯一要点是验证私有作用域的方法是否仍然可以引发事件,并不是我怀疑它,但我喜欢肯定。

        我运行了该项目,这导致将事件从“SimpleClass”的“simpleMethod”提升到主窗体,并转到名为 MainResponse 的预期正确方法,证明一个类确实可以引发一个消耗的事件由不同的班级。 是的,该事件必须从需要将其更改广播到其他关心的类的类中引发。接收类可以是一个类或多个类,具体取决于您定义它们的强类型或使它们像第 2 篇文章中那样单次转换。

        希望这会有所帮助,不要弄脏水。就我个人而言,我有很多代表和活动要清理!多播恶魔开始了!

        【讨论】:

          【解决方案10】:

          提升类必须获得EventHandler 的新副本。 以下是一种可能的解决方案。

          using System;
          
          namespace ConsoleApplication1
          {
              class Program
              {
                  class HasEvent
                  {
                      public event EventHandler OnEnvent;
                      EventInvoker myInvoker;
          
                      public HasEvent()
                      {
                          myInvoker = new EventInvoker(this, () => OnEnvent);
                      }
          
                      public void MyInvokerRaising() {
                          myInvoker.Raise();
                      }
          
                  }
          
                  class EventInvoker
                  {
                      private Func<EventHandler> GetEventHandler;
                      private object sender;
          
                      public EventInvoker(object sender, Func<EventHandler> GetEventHandler)
                      {
                          this.sender = sender;
                          this.GetEventHandler = GetEventHandler;
                      }
          
                      public void Raise()
                      {
                          if(null != GetEventHandler())
                          {
                              GetEventHandler()(sender, new EventArgs());
                          }
                      }
                  }
          
                  static void Main(string[] args)
                  {
                      HasEvent h = new HasEvent();
                      h.OnEnvent += H_OnEnvent;
                      h.MyInvokerRaising();
                  }
          
                  private static void H_OnEnvent(object sender, EventArgs e)
                  {
                      Console.WriteLine("FIRED");
                  }
              }
          }
          

          【讨论】:

          • 为什么将事件传递为 Func&lt;EventHandler&gt; GetEventHandler ?你可以直接从父类传递EvetnHandler
          • 我认为底层实现是一个值类型,所以你需要在使用它之前获取一个新的副本。否则,您将无法联系到在构建时间之后注册事件的任何观察者。
          • 有道理,我完全忽略了它。
          【解决方案11】:

          我在解决这个问题时采用了稍微不同的方法。我的解决方案包括一个 winform 前端、一个主类库 (DLL) 以及在该 dll 中的一个辅助工作类:

          WinForm |-----> PickGen 库 |---------> 分配类

          我决定做的是在分配类可以调用的主 dll (PickGen) 中创建事件,然后这些事件方法将在 UI 中调用事件。

          因此,allocations 在 PickGen 中引发了一个事件,该事件采用参数值并在表单中引发事件。从代码的角度来看,这是最底层的:

          public delegate void AllocationService_RaiseAllocLog(string orderNumber, string message, bool logToDatabase);
          public delegate void AllocationService_RaiseAllocErrorLog(string orderNumber, string message, bool logToDatabase);
          
          public class AllocationService { ...
              public event AllocationService_RaiseAllocLog RaiseAllocLog;
              public event AllocationService_RaiseAllocErrorLog RaiseAllocErrorLog;
          

          然后在子类代码中:

            RaiseAllocErrorLog(SOHNUM_0, ShipmentGenerated + ": Allocated line QTY was: " + allocatedline.QTY_0 + ", Delivered was: " + QTY_0 + ". Problem batch.", false);
          

          在主 DLL 类库中,我有这两个事件方法:

                  private void PickGenLibrary_RaiseAllocLog(string orderNumber, string message, bool updateDB)
              {
                  RaiseLog(orderNumber, message, false);
              }
              private void PickGenLibrary_RaiseAllocErrorLog(string orderNumber, string message, bool updateDB)
              {
                  RaiseErrorLog(orderNumber, message, false);
              }
          

          我在创建分配对象时在这里建立连接:

                      AllocationService allsvc = new AllocationService(PickResult);
          
                  allsvc.RaiseAllocLog += new AllocationService_RaiseAllocLog(PickGenLibrary_RaiseAllocLog);
                  allsvc.RaiseAllocErrorLog += new AllocationService_RaiseAllocErrorLog(PickGenLibrary_RaiseAllocErrorLog);
          

          然后我还设置了委托,以将主类与 winform 代码联系起来:

              public delegate void JPPAPickGenLibrary_RaiseLog(string orderNumber, string message, bool logToDatabase);
          public delegate void JPPAPickGenLibrary_RaiseErrorLog(string orderNumber, string message, bool logToDatabase);
          

          这可能不是最优雅的方式,但最终,它确实有效,而且不会太晦涩。

          【讨论】:

            【解决方案12】:

            使用public EventHandler AfterSearch; 不是 public event EventHandler AfterSearch;

            使用委托(Action 或 Func)而不是事件。事件本质上是一个只能从类中触发的委托。

            【讨论】:

              【解决方案13】:

              非常简单的例子。我喜欢使用 EventHandler 这样做。

                  class Program
                  {
                      static void Main(string[] args)
                      {
                          MyExtension ext = new MyExtension();
                          ext.MyEvent += ext_MyEvent;
                          ext.Dosomething();
                          Console.ReadLine();
                      }
              
                      static void ext_MyEvent(object sender, int num)
                      {
                          Console.WriteLine("Event fired.... "+num);
                      }
                  }
              
                  public class MyExtension
                  {
                      public event EventHandler<int> MyEvent;
              
                      public void Dosomething()
                      {
                          int no = 1;
              
                          if (MyEvent != null)
                              MyEvent(this, ++no);
                      }
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-05-05
                • 2010-10-03
                • 2010-10-21
                相关资源
                最近更新 更多