相当于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);
}
}
但是哪里有能力进行自定义饲养
其他答案告诉我,我不能在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());
}
}
}
你根本做不到。但是,由于只能从声明事件的类型内部引发事件,因此可以创建一个执行特定引发代码的辅助方法。然后只需确保您不会直接在该方法之外引发事件。
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 {}
语法。)