(我知道标题听起来很容易,但坚持 - 这可能不是你认为的问题。)
在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);
}
}
但是,自定义提升的能力在哪里?
答案 0 :(得分:9)
其他答案告诉我,我不能直接在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
代码部分基本上是一个事件和一个帮助函数。实际上,在VB.NET或C#中只有一个RaiseEvent
的实例是标准的protected OnTestEvent
方法并调用该方法来引发事件。这允许任何可以访问受保护(或私有或公共)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将告诉您的那样。
答案 1 :(得分:5)
在C#中,没有任何RaiseEvent块。你可以通过创建一个提升事件的方法来做同样的事情。
这是一个工作示例。在C#版本中,您甚至不需要使用添加和删除块 - 您可以使用默认实现,只需创建一个引发事件的自定义引发方法。
下面是一个工作程序(表单只是一个带有单个按钮的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());
}
}
}
答案 2 :(得分:2)
你根本做不到。但由于事件只能从声明它们的类型中引发,因此可以创建一个执行特定引发代码的辅助方法。然后确保不要直接在该方法之外引发事件。
答案 3 :(得分:1)
在C#中不存在如VB.NET中的AFAIK自定义事件引发。但是,您可以在lambda中包装实际的事件处理程序委托(作为add
传递给value
)并将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 {}
语法。)