相当于VB';s在C#中的自定义RaiseEvent块

本文关键字:自定义 RaiseEvent VB 相当于 | 更新日期: 2023-09-27 18:24:59

(我知道标题听起来很简单,但请稍等,这可能不是你认为的问题。)

在VB.NET中,我能够编写自定义事件。举个例子,我有一个单独的线程,它会定期引发一个事件,并且需要在该事件上更新GUI。我不想让繁忙的线程打扰UI计算,也不想把Me.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);
    }

}

但是哪里有能力进行自定义饲养

相当于VB';s在C#中的自定义RaiseEvent块

其他答案告诉我,我不能在C#中直接做到这一点,但没有告诉我为什么不能和为什么不想这样做的理由。我花了一段时间才理解C#事件与VB.NET相比是如何工作的。因此,这个解释是为了让其他不太了解这一点的人开始正确思考

老实说,我太习惯了样板OnTestEvent格式,所以我不太喜欢让它与其他辅助方法不同的想法。:-)但现在我明白了理由,我发现这实际上是放置这些东西的最佳位置


VB.NET允许您隐藏使用RaiseEvent关键字调用委托的后台详细信息。RaiseEvent调用事件委托或自定义事件的自定义RaiseEvent部分。

在C#中,没有RaiseEvent。引发一个事件基本上是,不过是调用一个委托。当您所做的只是调用委托时,不能无缝调用任何自定义RaiseEvent节。因此,对于C#来说,自定义事件就像骨架,实现了对事件的添加和删除,但没有实现引发它们的能力。这就像必须用自定义RaiseEvent部分的代码替换所有RaiseEvent TestEvent(sender, e)

对于正常事件,引发看起来大致类似于NormalEvent(sender, e)。但是,一旦放入自定义添加和删除,就必须使用添加和删除中使用的任何变量,因为编译器不再执行此操作。这就像VB.NET中的自动属性:一旦手动放入getter和setter,就必须声明和处理自己的本地变量。因此,使用testEventDelegate(sender, e)而不是TestEvent(sender, e)。这就是您重新安排活动代表的位置。


我将从VB.NET迁移到C#与必须用自定义RaiseEvent代码替换每个RaiseEvents进行了比较RaiseEvent代码段基本上是一个事件和一个助手函数实际上,标准做法是在受保护的OnTestEvent方法内部只有VB.NET或C#中的一个RaiseEvent实例,并调用该方法来引发事件。这允许任何访问受保护(或专用或公用)OnTest E事件的代码引发事件。对于您想要做的事情,只需将其放入方法中就更容易、更简单,并且执行稍微更好。这是最佳实践。

现在,如果你真的想(或需要)以某种方式模仿VB.NET的RaiseEvent的实质隐藏调用SomeDelegate(sender, e)并实现神奇的效果,那么只需将实质隐藏在第二个委托中即可:

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

现在您可以拨打NiceTestEvent(sender, e)。您将无法拨打TestEvent(sender, e)。正如VisualStudio将告诉您的那样,TestEvent仅用于外部代码的添加和删除。

在C#中,没有任何RaiseEvent块。你也可以通过创建一个引发事件的方法来做同样的事情。

下面是一个工作示例。在C#版本中,您甚至不需要使用add和remove块——您可以使用默认实现,只需创建一个引发事件的自定义引发方法。

下面是一个工作程序(该表单只是一个带有单个按钮的Windows窗体)。

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

你根本做不到。但是,由于只能从声明事件的类型内部引发事件,因此可以创建一个执行特定引发代码的辅助方法。然后只需确保您不会直接在该方法之外引发事件。

C#中不存在VB.NET中的AFAIK自定义事件引发。但是,您可以将实际的事件处理程序委托(作为value传递给add)包装在lambda中,并将该lambda订阅到事件而不是原始委托:
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...!
    }
}

(注意,为了简洁起见,我使用了C#2匿名delegate {}语法。)