繁体   English   中英

从 C# 中的不同 class 引发 class 的事件

[英]Raise an event of a class from a different class in C#

我有一个 class,EventContainer.cs,其中包含一个事件,比如:

public event EventHandler AfterSearch;

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

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

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

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

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

这是 POSSIBLE ,但使用聪明的黑客。

灵感来自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());
        }
    }
}

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

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

看起来您正在使用Delegate 模式 在这种情况下,应在EventRaiser类上定义AfterSearch事件,而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
}

我也偶然发现了这个问题,因为我正在尝试从外部调用 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;
}

同意 Femaref - 并注意这是代表和事件之间的重要区别(例如,请参阅此博客条目以了解有关此差异和其他差异的详细讨论)。

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

有一个很好的方法来做到这一点。 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(..., ...);
     ...
}

提升类必须获得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");
        }
    }
}

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

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);
            }
        }
    }
}

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

我的解决方案是阅读书籍并更加熟悉委托和事件处理程序。 尽管我已经使用了它们很多年,但我从未对它们非常熟悉。 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 大部分包括系统委托都是多播的。 我发现多播意味着任何具有相同签名的事件处理程序都会收到引发的事件。 当我逐步执行代码并看到我的事件似乎被错误地发送到我无意获取此事件的处理程序时,多播行为让我度过了一些不眠之夜。 两篇文章都解释了这种行为。 第二篇文章向您展示了一种方式,而第一篇文章则向您展示了另一种方式,即紧密键入委托和签名。 我个人认为强类型可以防止难以找到的愚蠢错误。 所以我会投票给第一篇文章,即使我让第二篇文章的代码有效。 我只是好奇而已。 :-)

我也很好奇我是否可以让 #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”实例。 这是早先在 Main 表单代码中定义和实例化的。

在 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 篇文章中那样将它们设为单一类型。

希望这会有所帮助,而不是将水弄混。 就我个人而言,我有很多代表和事件需要清理! 多播恶魔开始了!

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

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

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

因此,分配在 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);

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

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

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

嵌套的 class 与构造函数中提供的外部 class 的实例甚至可以访问外部 class 的私有成员。 正如这里更多解释: 关于内部类的stackoverflow问题 这包括在外部 class 中引发事件的能力。 这个 EventRaisers class 可以是内部的,或者以其他方式控制,因为从技术上讲,它可以由任何引用外部 class 实例的脚本创建。

很简单的例子。 我喜欢使用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);
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM