相当于 C# 中 VB 的自定义 RaiseEvent 块?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9342523/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Equivalent of VB's custom RaiseEvent blocks in C#?
提问by jnm2
(I know the title sounds easy, but hold on—this probably isn't the question you think it is.)
(我知道标题听起来很简单,但请稍等——这可能不是你认为的问题。)
In VB.NET I was able to write custom events. For an example, I had a separate thread that would periodically raise an event and on that event the GUI would need to be updated. I didn't want the busy thread to bother with UI calculations and I didn't want to put Me.Invoke(Sub() ...) in the event handler since it was also called from the GUI thread.
在 VB.NET 中,我能够编写自定义事件。例如,我有一个单独的线程,它会定期引发一个事件,并且在该事件上需要更新 GUI。我不想让忙碌的线程打扰 UI 计算,也不想将 Me.Invoke(Sub() ...) 放在事件处理程序中,因为它也是从 GUI 线程调用的。
I came up with this very useful bit of code. The GUI thread would set EventSyncInvoke = Me (the main form). The thread could then simply raise the event TestEvent as usual, no special code, and it would be seamlessly executed on the GUI thread:
我想出了这段非常有用的代码。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
Now in C# I can do this much:
现在在 C# 中,我可以做到这一点:
public event EventHandler TestEvent
add
{
testEventDelegate = (EventHandler)Delegate.Combine(testEventDelegate, value);
}
remove
{
testEventDelegate = (EventHandler)Delegate.Remove(testEventDelegate, value);
}
}
But where is the ability to do custom raising?
但是定制提升的能力在哪里?
采纳答案by jnm2
The other answers told me the fact that I couldn't do this directly in C#, but not the rationale behind why I can't and why I wouldn't want to. It took me a while to understand how C# events worked in comparison to VB.NET. So this explanation is for others who don't have a good grasp on this to start thinking along the right lines.
其他答案告诉我一个事实,我不能直接在 C# 中做到这一点,但不是我为什么不能和为什么我不想这样做的理由。我花了一段时间才理解 C# 事件与 VB.NET 相比是如何工作的。所以这个解释是为了其他没有很好理解的人开始沿着正确的思路思考。
Honestly, I was so used to the boilerplate OnTestEventformat that I didn't quite like the idea of making it different from the rest of the helper methods. :-) But now that I understand the rationale, I see that it is actually the best place to put this stuff.
老实说,我太习惯样板OnTestEvent格式了,所以我不太喜欢让它与其他辅助方法不同的想法。:-) 但是现在我明白了基本原理,我发现它实际上是放置这些东西的最佳位置。
VB.NET allows you to hide the background details of calling the delegates with the RaiseEventkeyword. RaiseEventcalls either the event delegate or your custom RaiseEventsection for a custom event.
VB.NET 允许您隐藏使用RaiseEvent关键字调用委托的背景细节。 为自定义事件RaiseEvent调用事件委托或您的自定义RaiseEvent部分。
In C#, there is no RaiseEvent. Raising an event is basicallyno more than calling a delegate. No custom RaiseEventsections can be seamlessly called when all you're doing to raise it is calling a delegate. So for C#, custom events are like skeletons, implementing add and remove for events but not implementing the ability to raise them. It's like having to replace all your RaiseEventTestEvent(sender, e)with the code from the custom RaiseEventsection.
在 C# 中,没有RaiseEvent. 引发一个事件基本上只不过是调用一个委托。RaiseEvent当您正在执行的所有操作都是调用委托时,无法无缝调用自定义部分。所以对于 C# 来说,自定义事件就像骨架,实现了事件的添加和删除,但没有实现引发它们的能力。这就像必须RaiseEventTestEvent(sender, e)用自定义RaiseEvent部分的代码替换所有内容。
For a normal event, raising looks roughly like NormalEvent(sender, e). But as soon as you put in a custom add and remove, you must use whatever variable you used in the add and remove because the compiler isn't doing it anymore. It's like automatic properties in VB.NET: once you put in a getter and setter manually, you have to declare and handle your own local variable. So instead of TestEvent(sender, e), use testEventDelegate(sender, e). That's where you rerouted the event delegates.
对于普通事件, raise 大致类似于NormalEvent(sender, e). 但是一旦您放入自定义添加和删除,您就必须使用您在添加和删除中使用的任何变量,因为编译器不再这样做了。这就像 VB.NET 中的自动属性:一旦您手动放入 getter 和 setter,您必须声明和处理您自己的局部变量。因此,不要TestEvent(sender, e)使用testEventDelegate(sender, e). 这就是您重新路由事件委托的地方。
I compared moving from VB.NET to C# with having to replace each of your RaiseEventswith your custom RaiseEventcode. A RaiseEventcode section is basically an event and a helper function rolled together.It's actually standard to only have one instance of a RaiseEventin either VB.NET or C# inside a protected OnTestEventmethod and call that method to raise the event. This allows any code with access to the protected (or private or public) OnTestEvent to raise the event. For what you want to do, just putting it in the method is easier, simpler and performs slightlybetter. This is best practice.
我将从 VB.NET 迁移到 C# 与必须RaiseEvents用自定义RaiseEvent代码替换每个代码进行了比较。 甲RaiseEvent代码段基本上是一个事件,并卷在一起的辅助功能。RaiseEvent在受保护的OnTestEvent方法中 只有一个VB.NET 或 C# 中的 a 实例并调用该方法来引发事件实际上是标准的。这允许任何有权访问受保护(或私有或公共)OnTestE通风口的代码引发事件。对于你想做的事情,只要把它放在方法中就更容易、更简单并且性能稍好一些。这是最佳实践。
Now if you really want to want (or need) somehow to mimic VB.NET's RaiseEvent nitty-gritty-hiding call SomeDelegate(sender, e)and have the magic happen, you cansimply hide the nitty-gritty inside a second delegate:
现在,如果你真的想要(或需要)以某种方式模仿 VB.NET 的 RaiseEvent 本质隐藏调用SomeDelegate(sender, e)并让魔法发生,你可以简单地将本质隐藏在第二个委托中:
NiceTestEvent = (sender, e) => eventSyncInvoke.Invoke(testEventDelegate, new object[] { sender, e });
NiceTestEvent = (sender, e) => eventSyncInvoke.Invoke(testEventDelegate, new object[] { sender, e });
Now you can call NiceTestEvent(sender, e). You won't be able to call TestEvent(sender, e)though. TestEventis only for outside code to add and remove through, as Visual Studio will tell you.
现在您可以调用NiceTestEvent(sender, e). 但是你将无法打电话TestEvent(sender, e)。 TestEvent仅用于添加和删除外部代码,正如 Visual Studio 会告诉您的那样。
回答by stakx - no longer contributing
AFAIK custom event raising as in VB.NET does not exist in C#. However, you could wrap the actual event handler delegates (passed to addas value) in a lambda and subscribe that lambda to the event instead of the original delegate:
C# 中不存在 VB.NET 中的 AFAIK 自定义事件引发。但是,您可以将实际的事件处理程序委托(传递给addas value)包装在 lambda 中,并将该 lambda 订阅到事件而不是原始委托:
add
{
testEventDelegate = Delegate.Combine(testEventDelegate, (s, e) => { ... } )
}
(Above code untested, syntax might be slightly off. I'll fix it as soon as I can test it.)
(以上代码未经测试,语法可能略有偏差。我会尽快修复它。)
Crude, but working example:
粗略但有效的例子:
The following is a concrete example of the above. I am not convinced myself that the following is good, solid code, nor that it would work in all circumstances (such as multi-threading etc.)... nevertheless, here it is:
以下是上述内容的具体示例。我不相信以下是好的、可靠的代码,也不相信它在所有情况下都可以工作(例如多线程等)……不过,这里是:
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...!
}
}
(Note that I've used the C# 2 anonymous delegate {}syntax for brevity.)
(请注意,delegate {}为简洁起见,我使用了 C# 2 匿名语法。)
回答by svick
You simply can't. But since events can be raised only from inside the type that declares them, you can create a helper method that executes your specific raising code. And then just make sure you don't raise the event directly outside that method.
你根本做不到。但是由于事件只能从声明它们的类型内部引发,您可以创建一个辅助方法来执行您的特定引发代码。然后确保您不会直接在该方法之外引发事件。
回答by JMarsch
In C#, there isn't any RaiseEvent block. You would do the same thing by creating a method for raising your event.
在C# 中,没有任何 RaiseEvent 块。您可以通过创建引发事件的方法来做同样的事情。
Here is a working example. In the C# version, you do not even need to use the add and remove block -- you can use the default implementation for that and just create a custom raise method that raises your event.
这是一个工作示例。在 C# 版本中,您甚至不需要使用 add 和 remove 块——您可以使用默认实现,只需创建一个引发事件的自定义 raise 方法。
Below is a working program (the form is just a Windows Forms form with a single button on it).
下面是一个工作程序(该窗体只是一个带有单个按钮的 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());
}
}
}

