RaiseEvent в C#

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

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

Я пришел это очень полезный кусок кода. Поток 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

теперь в C# я могу сделать это:

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


}

но где возможность сделать custom повышение?

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

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

4 ответов


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

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

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


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

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

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

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


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

редактировать

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

Ниже приведена рабочая программа (форма-это просто форма 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());
        }
    }
}

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


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

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

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


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

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

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...!
    }
}

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