Подтвердить что ты не робот

Поднять событие класса из другого класса в С#

У меня есть класс EventContainer.cs, который содержит событие, например:

public event EventHandler AfterSearch;

У меня есть еще один класс EventRaiser.cs. Как я могу (и не обрабатывать) вышеупомянутое событие из этого класса?

Поднятое событие, в свою очередь, вызовет обработчик события в классе EventContainer. Что-то вроде этого (это, очевидно, неверно):

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

Ответ 1

Это невозможно, события могут быть подняты только внутри класса. Если бы вы могли это сделать, это бы побеждало цель событий (возможность повышения изменений статуса внутри класса). Я думаю, что вы неправильно понимаете функцию событий - событие определено внутри класса, а другие могут подписаться на него, выполняя

obj.AfterSearch += handler; (где обработчик - это метод согласно сигнатуре AfterSearch). Один может подписаться на событие извне просто отлично, но он может быть поднят только внутри класса, определяющего его.

Ответ 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());
        }
    }
}

Ответ 3

Похоже, вы используете шаблон делегирования. В этом случае событие 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
}

Ответ 4

Вы можете написать публичный метод в классе, в котором вы хотите, чтобы событие срабатывало и запускало событие при его вызове. Затем вы можете вызвать этот метод у любого пользователя вашего класса.

Конечно, это разрушает инкапсуляцию и плохой дизайн.

Ответ 5

Есть хороший способ сделать это. Каждое событие в С# имеет делегат, который указывает знак методов для этого события. Определите поле в своем внешнем классе с типом делегата события. получите ссылку этого поля в конструкторе внешнего класса и сохраните его. В основном классе вашего мероприятия отправьте ссылку события для делегата внешнего класса. Теперь вы можете легко вызвать делегата во внешнем классе.

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(..., ...);
     ...
}

Ответ 6

Не хорошее программирование, но если вы хотите сделать это любым способом, вы можете сделать что-то вроде этого

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

Ответ 7

Согласитесь с Femaref - и обратите внимание, что это важная разница между делегатами и событиями (см., например, эту запись в блоге для хорошего обсуждения это и другие отличия).

В зависимости от того, чего вы хотите достичь, вам может быть лучше с делегатом.

Ответ 8

Я также наткнулся на эту проблему, потому что я экспериментировал с вызовом событий 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;
}

Ответ 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 большинство, включая делегатов системы, являются многоадресными. Я обнаружил, что многоадресная рассылка означает, что любой обработчик событий с той же подписью получит поднятое событие. Многоадресное поведение вызвало у меня несколько бессонных ночей, когда я шагнул через код и увидел, что мое событие, казалось бы, ошибочно отправлено обработчикам, что я не собирался получать это событие. Обе статьи объясняют это поведение. Вторая статья показывает вам один путь, и первая статья показывает вам другую, сделав делегата и подпись жестко напечатанной. Я лично считаю, что сильная типизация предотвращает глупые ошибки, которые могут быть причиной боли. Поэтому я проголосовал за первую статью, хотя я получил второй код статьи. Мне было просто любопытно.:-)

Мне также было любопытно, могу ли я получить код статьи № 2, чтобы вести себя так, как я интерпретировал исходный вопрос выше. Независимо от выбранного вами подхода или если я также неправильно истолковываю исходный вопрос, мое настоящее сообщение состоит в том, что я все еще думаю, что вам будет полезно прочитать первую статью, как и я, особенно если вопросы или ответы на этой странице оставят вас в замешательстве. Если у вас многодневные кошмары и вам нужно быстрое решение, вам может помочь статья 2.

Я начал играть со вторым классом eventRaiser. Я сделал простой проект в виде окон. Я добавил в свой проект второй класс статей 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, доказывая, что один класс действительно может вызвать событие, которое потребляется другим классом. Да, событие должно быть поднято из класса, который требует, чтобы он менял трансляцию на другие классы, которые заботятся. Прием классов может быть одним классом или многими многими классами в зависимости от того, насколько сильно вы набрали их, или сделав их одиночными, как в 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");
        }
    }
}

Ответ 11

Очень простой пример. Мне нравится делать это таким образом, используя 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);
        }
    }
}

Ответ 12

Я принял несколько иной подход к решению этой проблемы. Мое решение состояло из внешнего интерфейса winform, главной библиотеки классов (DLL) и внутри этого dll вторичного рабочего класса:

WinForm | --→ Библиотека PickGen | -----→ Класс выделений

Я решил создать события в основной dll (PickGen), которые мог бы вызывать класс 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);

Возможно, это не самый элегантный способ сделать это, но, в конце концов, он работает и не слишком неясен.