RaiseEvent в С#

(Я знаю, что название звучит просто, но держись, наверное, это не вопрос, который вы так думаете.)

В VB.NET мне удалось написать пользовательские события. Например, у меня был отдельный поток, который периодически поднимал событие, и в этом случае графический интерфейс должен был быть обновлен. Я не хотел, чтобы занятый поток беспокоился об вычислениях пользовательского интерфейса, и я не хотел помещать Me.Invoke(Sub()...) в обработчик события, так как он также вызывался из потока графического интерфейса.

Я придумал этот очень полезный бит кода. В потоке GUI будет установлен EventSyncInvoke = Me (основная форма). Затем поток мог просто поднять событие TestEvent, как обычно, никакого специального кода, и он будет легко выполнен в потоке GUI:

Private TestEventDelegate As EventHandler
Public EventSyncInvoke As System.ComponentModel.ISynchronizeInvoke

Public Custom Event TestEvent As EventHandler
    AddHandler(value As EventHandler)
        TestEventDelegate = [Delegate].Combine(TestEventDelegate, value)
    End AddHandler

    RemoveHandler(value As EventHandler)
        TestEventDelegate = [Delegate].Remove(TestEventDelegate, value)
    End RemoveHandler

    RaiseEvent(sender As Object, e As System.EventArgs)
        If EventSyncInvoke IsNot Nothing Then
            EventSyncInvoke.Invoke(TestEventDelegate, {sender, e})
        Else
            TestEventDelegate.Invoke({sender, e})
        End If
    End RaiseEvent
End Event

Теперь в С# я могу сделать так много:

public event EventHandler TestEvent
    add
    {
        testEventDelegate = (EventHandler)Delegate.Combine(testEventDelegate, value);
    }
    remove
    {
        testEventDelegate = (EventHandler)Delegate.Remove(testEventDelegate, value);
    }


}

Но где можно выполнять индивидуальное повышение?

EDIT:
Другие ответы сказали мне, что я не мог сделать это прямо на С#, но не объяснил, почему я не могу и почему я этого не хочу. Мне потребовалось некоторое время, чтобы понять, как С# события работали по сравнению с VB.NET. Я оставил мое собственное объяснение для других, у кого нет хорошего понимания этого, чтобы начать думать по правильным линиям.

Честно говоря, я так привык к шаблону OnTestEvent, что мне не совсем понравилась идея сделать его отличным от остальных вспомогательных методов.:-) Теперь, когда я понимаю обоснование, я вижу, что на самом деле это лучшее место для размещения этого материала.

Ответ 1

VB.NET позволяет скрыть данные фона для вызова делегатов с ключевым словом RaiseEvent. RaiseEvent вызывает либо делегат события, либо ваш пользовательский раздел RaiseEvent для настраиваемого события.

В С# нет RaiseEvent. Поднятие события в основном не более, чем вызов делегата. Никакие пользовательские разделы RaiseEvent не могут быть легко вызваны, когда все, что вы делаете, чтобы поднять его, вызывает делегат. Таким образом, для С# пользовательские события похожи на скелеты, реализующие добавление и удаление для событий, но не реализующие возможности их повышения. Это похоже на необходимость заменить весь ваш RaiseEvent TestEvent (отправитель, e) кодом из пользовательской секции RaiseEvent.

Для обычного события повышение выглядит примерно как NormalEvent (отправитель, e). Но как только вы добавите пользовательское дополнение и удаление, вы должны использовать любую переменную, которую вы использовали в add и remove, потому что компилятор больше этого не делает. Это похоже на автоматические свойства в VB.NET: после того, как вы вручную установили геттер и сеттер, вы должны объявить и обработать свою собственную локальную переменную. Поэтому вместо TestEvent (sender, e) используйте testEventDelegate (отправитель, e). Это, когда вы перенаправили делегатов мероприятия.


Я сравнивал переход от VB.NET к С# с заменой каждого из ваших RaiseEvents на ваш пользовательский код RaiseEvent. Раздел кода RaiseEvent - это, в основном, событие и вспомогательная функция, свернутая вместе. На самом деле стандартно иметь только один экземпляр RaiseEvent в VB.NET или С# внутри защищенного метода OnTestEvent и вызывать этот метод для поднимите событие. Это позволяет любому коду с доступом к защищенному (или частному или общедоступному) OnTestEvent, чтобы поднять это событие. Для того, что вы хотите сделать, просто поместить его в метод проще, проще и немного улучшить. Это наилучшая практика.

Теперь, если вы действительно хотите (или нуждаетесь) каким-то образом имитировать VB.NET RaiseEvent nitty-gritty-hiding call SomeDelegate (sender, e) и совершить магию, вы можете просто скрыть nitty-gritty внутри секунды делегат:

NiceTestEvent = (sender, e) => eventSyncInvoke.Invoke(testEventDelegate, new object[] { sender, e });

Теперь вы можете позвонить NiceTestEvent (отправитель, e). Вы не сможете назвать TestEvent (отправитель, e). TestEvent предназначен только для внешнего кода для добавления и удаления, как сообщит вам Visual Studio.

Ответ 2

В С# нет никакого блока RaiseEvent. Вы бы сделали то же самое, создав метод для повышения вашего события.

Edit

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

Ниже приведена рабочая программа (форма - это просто форма Windows Forms с одной кнопкой на ней).

// Here is your event-raising class
using System;
using System.ComponentModel;

namespace ClassLibrary1
{
    public class Class1
    {
        public ISynchronizeInvoke EventSyncInvoke { get; set; }
        public event EventHandler TestEvent;


        private void RaiseTestEvent(EventArgs e)
        {
            // Take a local copy -- this is for thread safety.  If an unsubscribe on another thread
            // causes TestEvent to become null, this will protect you from a null reference exception.
            // (The event will be raised to all subscribers as of the point in time that this line executes.)
            EventHandler testEvent = this.TestEvent;

            // Check for no subscribers
            if (testEvent == null)
                return;

            if (EventSyncInvoke == null)
                testEvent(this, e);
            else
                EventSyncInvoke.Invoke(testEvent, new object[] {this, e});
        }

        public void Test()
        {
            RaiseTestEvent(EventArgs.Empty);
        }
    }
}

// Here is a form that tests it -- if you run it, you will see that the event is marshalled back to
// the main thread, as desired.
using System;
using System.Threading;
using System.Windows.Forms;

namespace ClassLibrary1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.TestClass = new Class1();
            this.TestClass.EventSyncInvoke = this;
            this.TestClass.TestEvent += new EventHandler(TestClass_TestEvent);
            Thread.CurrentThread.Name = "Main";
        }

        void TestClass_TestEvent(object sender, EventArgs e)
        {
            MessageBox.Show(this, string.Format("Event.  Thread: {0} Id: {1}", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId));
        }

        private Class1 TestClass;

        private void button1_Click(object sender, EventArgs e)
        {
            // You can test with an "old fashioned" thread, or the TPL.
            var t = new Thread(() => this.TestClass.Test());
            t.Start();
            //Task.Factory.StartNew(() => this.TestClass.Test());
        }
    }
}

Ответ 3

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

Ответ 4

Подстройка пользовательского события AFAIK, как в VB.NET, не существует на С#. Тем не менее, вы можете обернуть делегаты обработчика фактических событий (переданные в add как value) в лямбда и подписаться, что лямбда для события вместо исходного делегата:

add 
{ 
    testEventDelegate = Delegate.Combine(testEventDelegate, (s, e) => { ... } ) 
} 

(Выше кода непроверенный, синтаксис может быть немного выключен. Я исправлю его, как только смогу проверить его.)


Грубый, но рабочий пример:

Ниже приведен конкретный пример вышеизложенного. Я не уверен, что следующее: хороший, прочный код, и что он будет работать во всех обстоятельствах (например, многопоточность и т.д.)... тем не менее, вот он:

class Foo
{
    public Foo(SynchronizationContext context)
    {
        this.context = context ?? new SynchronizationContext();
        this.someEventHandlers = new Dictionary<EventHandler, EventHandler>();
    }

    private readonly SynchronizationContext context;
    // ^ could also use ISynchronizeInvoke; I chose SynchronizationContext
    //   for this example because it is independent from, but compatible with,
    //   Windows Forms.

    public event EventHandler SomeEvent
    {
        add
        {
            EventHandler wrappedHandler = 
                (object s, EventArgs e) =>
                {
                    context.Send(delegate { value(s, e); }, null);
                    // ^ here is where you'd call ISynchronizeInvoke.Invoke().
                };
            someEvent += wrappedHandler;
            someEventHandlers[value] = wrappedHandler;
        }
        remove
        {
            if (someEventHandlers.ContainsKey(value))
            {
                someEvent -= someEventHandlers[value];
                someEventHandlers.Remove(value);
            }
        }
    }
    private EventHandler someEvent = delegate {};
    private Dictionary<EventHandler, EventHandler> someEventHandlers;

    public void RaiseSomeEvent()
    {
        someEvent(this, EventArgs.Empty);
        // if this is actually the only place where you'd invoke the event,
        // then you'd have far less overhead if you moved the ISynchronize-
        // Invoke.Invoke() here and forgot about all the wrapping above...!
    }
}

(Обратите внимание, что я использовал анонимный синтаксис С# 2 delegate {} для краткости.)