(我知道标题听起来很容易,但坚持 - 这可能不是你认为的问题.)
在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
Run Code Online (Sandbox Code Playgroud)
现在在C#中,我可以做到这一点:
public event EventHandler TestEvent
add
{
testEventDelegate = (EventHandler)Delegate.Combine(testEventDelegate, value);
}
remove
{
testEventDelegate = (EventHandler)Delegate.Remove(testEventDelegate, value);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,定制养殖的能力在哪里?
jnm*_*nm2 10
其他答案告诉我,我不能直接在C#中这样做,但不能解释为什么我不能这样做以及为什么我不想这样做.我花了一些时间来理解C#事件与VB.NET相比如何工作.所以这个解释是针对那些对此没有很好把握的人开始思考正确的思路.
老实说,我已经习惯了样板OnTestEvent格式,我不太喜欢让它与其他辅助方法不同的想法.:-)但是现在我理解了它的基本原理,我发现它实际上是放置这些东西的最佳位置.
VB.NET允许您隐藏使用RaiseEvent关键字调用委托的背景细节. 为自定义事件RaiseEvent调用事件委托或自定义RaiseEvent部分.
在C#中,没有RaiseEvent.举办活动基本上不过是召集代表.RaiseEvent当您所做的所有事情都是调用委托时,不能无缝调用自定义部分.因此对于C#,自定义事件就像骨架,实现事件的添加和删除,但没有实现提升它们的能力.这就像必须RaiseEvent TestEvent(sender, e)用自定义RaiseEvent部分中的代码替换所有代码.
对于正常事件,提升看起来大致相同NormalEvent(sender, e).但是只要你自定义添加和删除,你必须使用你在添加和删除中使用的任何变量,因为编译器不再这样做了.这就像VB.NET中的自动属性:一旦手动输入getter和setter,就必须声明并处理自己的局部变量.所以不要TestEvent(sender, e)使用testEventDelegate(sender, e).这是您重新路由事件代表的地方.
我比较了从VB.NET到C#的转换,必须RaiseEvents用自定义RaiseEvent代码替换每个. 甲RaiseEvent代码段基本上是一个事件,并卷在一起的辅助功能. 实际上,RaiseEvent在受保护的OnTestEvent方法中只有VB.NET或C#中的一个实例并调用该方法来引发事件是标准的.这允许任何可以访问受保护(或私有或公共)OnTest E通风口的代码来引发事件.对于您想要做的事情,只需将其放入方法中就会更容易,更简单并且性能稍好一些.这是最佳做法.
现在,如果你真的想要(或需要)以某种方式模仿VB.NET的RaiseEvent细节隐藏调用SomeDelegate(sender, e)并让魔法发生,你可以简单地隐藏第二个委托内的细节:
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());
}
}
}
Run Code Online (Sandbox Code Playgroud)