我正在学习C#中的事件/代表。我可以问一下你对我所选择的命名/编码风格的看法(取自Head First C#书)吗?
明天我正在教朋友这件事,并且正在努力想出最优雅的方式来解释这些概念。 (想到理解一个主题的最好方法就是尝试教它!)class Program
{
static void Main()
{
// setup the metronome and make sure the EventHandler delegate is ready
Metronome metronome = new Metronome();
// wires up the metronome_Tick method to the EventHandler delegate
Listener listener = new Listener(metronome);
metronome.OnTick();
}
}
public class Metronome
{
// a delegate
// so every time Tick is called, the runtime calls another method
// in this case Listener.metronome_Tick
public event EventHandler Tick;
public void OnTick()
{
while (true)
{
Thread.Sleep(2000);
// because using EventHandler delegate, need to include the sending object and eventargs
// although we are not using them
Tick(this, EventArgs.Empty);
}
}
}
public class Listener
{
public Listener(Metronome metronome)
{
metronome.Tick += new EventHandler(metronome_Tick);
}
private void metronome_Tick(object sender, EventArgs e)
{
Console.WriteLine("Heard it");
}
}
n.b。代码是从http://www.codeproject.com/KB/cs/simplesteventexample.aspx
重构的答案 0 :(得分:64)
Microsoft实际上编写了大量的命名准则并将其放在MSDN库中。你可以在这里找到文章:Guidelines for Names
除了一般大写指南之外,以下是Names of Type Members页面上“活动”的内容:
使用动词或动词命名事件 短语。
给事件名称一个概念 使用现在之前和之后 和过去时。例如,关闭 在窗口之前引发的事件 关闭将被称为关闭和 在窗口之后被抬起的那个 关闭将被称为关闭。
请勿使用Before或After前缀或 后缀表示前后 事件
命名事件处理程序(使用委托 作为事件的类型) EventHandler后缀。
请使用名为sender和的两个参数 e在事件处理程序签名中。
sender参数应为type 对象和e参数应该是 一个实例或继承自 EventArgs的。
使用命名事件参数类 EventArgs后缀。
答案 1 :(得分:53)
我要提几点:
Metronome.OnTick似乎没有被正确命名。在语义上,“OnTick”告诉我它将在“Tick”时被调用,但这并不是真正发生的事情。我会称之为“Go”。
通常接受的模型是执行以下操作。 OnTick
是一种引发事件的虚拟方法。这样,您可以轻松地覆盖继承类中的默认行为,并调用基类来引发事件。
class Metronome
{
public event EventHandler Tick;
protected virtual void OnTick(EventArgs e)
{
//Raise the Tick event (see below for an explanation of this)
var tickEvent = Tick;
if(tickEvent != null)
tickEvent(this, e);
}
public void Go()
{
while(true)
{
Thread.Sleep(2000);
OnTick(EventArgs.Empty); //Raises the Tick event
}
}
}
另外,我知道这是一个简单的例子,但是如果没有附加监听器,你的代码就会抛出Tick(this, EventArgs.Empty)
。你应该至少包括一个空值守卫来检查听众:
if(Tick != null)
Tick(this, EventArgs.Empty);
但是,如果侦听器在调用和调用之间取消注册,则在多线程环境中仍然容易受到攻击。最好的方法是首先捕获当前的侦听器并调用它们:
var tickEvent = Tick;
if(tickEvent != null)
tickEvent(this, EventArgs.Empty);
我知道这是一个陈旧的答案,但由于它仍在收集投票,这里是C#6的做事方式。整个“guard”概念可以用条件方法调用替换,编译器确实在捕获侦听器方面做了Right Thing(TM):
Tick?.Invoke(this, EventArgs.Empty);
答案 2 :(得分:15)
我想说一般事件的最佳指南,包括命名约定,是here。
这是我采用的惯例,简要说明:
答案 3 :(得分:7)
有趣的是,微软似乎打破了自己的命名约定,使用Visual Studio生成的事件处理程序名称。
答案 4 :(得分:4)
我在.Net中使用事件多年后发现的一点是重复需要在每次调用时检查事件是否为空处理程序。我还没有看到一段可以执行任何操作的实时代码,但如果它为null则不会调用该事件。
我开始做的是在我创建的每个事件上放置一个虚处理程序,以节省进行空检查的需要。
public class Metronome
{
public event EventHandler Tick =+ (s,e) => {};
protected virtual void OnTick(EventArgs e)
{
Tick(this, e); // now it's safe to call without the null check.
}
}
答案 5 :(得分:2)
除了OnTick
不遵循典型的事件调用模型这一事实外,看起来不错。通常情况下,On[EventName]
会一次性提升事件,例如
protected virtual void OnTick(EventArgs e)
{
if(Tick != null) Tick(this, e);
}
考虑创建此方法,并将现有的“OnTick
”方法重命名为“StartTick
”,而不是直接从Tick
调用StartTick
,请致电{{1来自OnTick(EventArgs.Empty)
方法。
答案 6 :(得分:2)
在你的情况下,它可能是:
class Metronome {
event Action Ticked;
internalMethod() {
// bla bla
Ticked();
}
}
上面的sampple使用下面的约定,自我描述;]
活动来源:
class Door {
// case1: property change, pattern: xxxChanged
public event Action<bool> LockStateChanged;
// case2: pure action, pattern: "past verb"
public event Action<bool> Opened;
internalMethodGeneratingEvents() {
// bla bla ...
Opened(true);
LockStateChanged(false);
}
}
顺便说一句。关键字event
是可选的,但可以将“事件”与“回调”区分开来
事件监听器:
class AlarmManager {
// pattern: NotifyXxx
public NotifyLockStateChanged(bool state) {
// ...
}
// pattern: [as above]
public NotifyOpened(bool opened) {
// OR
public NotifyDoorOpened(bool opened) {
// ...
}
}
绑定[代码看起来很友好]
door.LockStateChanged += alarmManager.NotifyLockStateChanged;
door.Moved += alarmManager.NotifyDoorOpened;
即使手动发送事件也是“人类可读的”。
alarmManager.NotifyDoorOpened(true);
有时更具表现力的可能是“动词+ ing”
dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting;
无论您选择哪种惯例,都要与之保持一致。