在C#中从不同的类中引发类的事件

时间:2010-12-07 15:28:12

标签: c# events

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

public event EventHandler AfterSearch;

我有另一个类EventRaiser.cs。如何从这个课程中提出(而不是处理)上述事件?

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

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

12 个答案:

答案 0 :(得分:35)

这是不可能的,事件只能从课堂内升起。如果你能做到这一点,那就会破坏事件的目的(能够从课堂内部提升状态)。我认为你误解了事件的功能 - 事件是在一个类中定义的,而其他人可以通过执行来订阅它

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

答案 1 :(得分:21)

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

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

答案 2 :(得分:8)

看起来你正在使用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
}

答案 3 :(得分:7)

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

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

答案 4 :(得分:4)

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

答案 5 :(得分:4)

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

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

答案 6 :(得分:3)

同意Femaref - 请注意这是代表和事件之间的重要区别(例如,请参阅this blog entry,以便对此讨论和其他差异进行讨论)。

根据您想要达到的目标,您可能会更好地与代表合作。

答案 7 :(得分:2)

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

答案 8 :(得分:0)

我有类似的困惑,诚实地发现这里的答案令人困惑。虽然一对夫妇暗示我后来会发现可以解决的解决方案。

我的解决方案是阅读书籍,熟悉代表和事件处理程序。 虽然我已经使用了很多年,但我从未对它们非常熟悉。 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);
   }

请注意" eventRaiser"的实例我传递给SimpleClass.cs。这是在早期的主表单代码中定义和实例化的。

在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的私有方法的唯一要点是验证私有范围的方法仍然可以引发事件,而不是我怀疑它,但我喜欢积极。

我运行了这个项目,这导致了来自" simpleMethod" " SimpleClass"直到主窗体并转到名为MainResponse的预期正确方法,证明一个类确实可以引发由另一个类消耗的事件。 是的,必须从需要它的班级内提出这一事件,将其转播到其他关心的班级。接收类可以是一个类或许多类,具体取决于您定义它们的强度类型,或者如第二篇文章中那样对它们进行单一演员。

希望这有助于而不是浑水。就个人而言,我有很多代表和活动需要清理!多播恶魔开始了!

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

在解决此问题时,我采取了略有不同的方法。我的解决方案包括一个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);

这可能不是最优雅的方法,但最终它确实可以工作并且不会太晦涩。