Эквивалент пользовательских блоков VB RaiseEvent в C #? - PullRequest
12 голосов
/ 18 февраля 2012

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

В VB.NET я мог писать собственные события.Например, у меня был отдельный поток, который периодически вызывал событие, и в этом случае GUI нужно было бы обновлять.Я не хотел, чтобы занятый поток беспокоился о вычислениях пользовательского интерфейса, и я не хотел помещать 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

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

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


}

Но где здесь возможность сделать кастом рейз ?

Ответы [ 4 ]

9 голосов
/ 18 февраля 2012

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

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


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

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

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


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

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

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

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

5 голосов
/ 18 февраля 2012

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

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

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

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

1 голос
/ 18 февраля 2012

Настраиваемое событие AFAIK, как в VB.NET, не существует в C #. Однако вы можете заключить фактические делегаты обработчика событий (переданные в 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...!
    }
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...