为什么 MSDN 建议在委托声明中包含对象发送者?
我正在阅读此页面,我注意到它是如何说这是标准指南的:
.NET Framework 指南指出,用于事件的委托类型应采用两个参数,一个是指示事件源的“对象源”参数,另一个是封装有关事件的任何其他信息的“e”参数。
我可以理解在某些情况下拥有对象发送者
如何有用,但我可以看到在其他情况下完全相反。例如,
如果处理事件的类不应该知道谁触发了该事件怎么办?耦合、内聚等等。
就我而言,我已经将对象引用作为成员变量。这就是我订阅该活动的方式。它只会有一个实例,因此没有理由强制转换发送者对象而不是仅使用成员变量。
在我的程序中,客户端根本不应该知道发送者对象。很难解释我在做什么,但基本上我在一个库中有一个带有内部构造函数的类,该库中的其他两个类也使用该类。我的客户端类正在订阅来自这两个类的事件,但这些事件最初是从客户端不应该了解的这个内部类调用的。
这会让事件处理程序的客户端感到困惑。库应该易于理解,就我而言,没有理由使用 sender 变量。没有任何。那为什么要包含它呢?
话虽这么说,为什么 Microsoft 表明事件处理程序应该遵循这些准则?这不总是最好的选择吗?
编辑:感谢大家的回复。我决定与大多数人一起使用 EventHandler
来处理此库中的所有事件。
I was reading this page and I noticed how it said this is standard guidelines:
The .NET Framework guidelines indicate that the delegate type used for an event should take two parameters, an "object source" parameter indicating the source of the event, and an "e" parameter that encapsulates any additional information about the event.
I can understand how having an object sender
could be useful in some circumstances, but I could see the exact opposite in others. For example,
What if a class handling the event should not have any knowledge about who fired it? Coupling, cohesion, and all of that.
In my case, I already have a reference to the object as a member variable. That is how I subscribe to the event. There will only ever be one instance of it so there's no reason to cast the sender object rather than just using the member variable.
In my program the sender object should not be known at all to the clients. It's hard to explain what I am doing but basically I have a class with an internal constructor within a library that is used by two other classes also within that library. My client classes are subscribing to events from those two classes but the events are originally invoked from this internal class that clients should not have any knowledge of.
It is confusing to clients of the event handler. Libraries should be simple to understand and in my case, there is no reason to ever use the sender variable. None. Then why include it?
That being said, why does Microsoft indicate that event handlers should follow these guidelines? Isn't it not always the best choice?
EDIT: Thanks for the replies everyone. I've decided to go with the majority and use EventHandler<T>
for all my events in this library.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
你是在逆风而行,.NET框架有一定的设计、规则和指导方针,在使用它时,如果你想正确使用它,你就应该遵循这些指导。
如果您使用原始委托,您将拥有所需的所有自由,但如上所述,如果您要为事件声明委托类型,则还应包含
sender
对象和EventArgs
对象(基类或派生类)。如果您违反了这些规则,正如我刚才在回答您的其他问题时所说: 我应该使用 EventArgs 还是简单的数据类型?,您可能最终会遇到代码中断的情况。
简单地说,当框架在控件上调用 OnClick 事件时,.NET Framework 确实会传递发送者和
EventArgs
实例...如果事件不符合要求,则可能会出现问题。如果你想要完全的自由,那么使用简单的委托而不是事件。
You are fighting against the wind, the .NET framework has certain design, rules and guidelines and when using it, if you want to use it correctly, you are supposed to follow those directions.
if you use raw delegates you have all the freedom you want but as stated above if you are declaring a delegate type for an event you should include
sender
object andEventArgs
object as well (base or derived class).if you break those rules, as I said a moment ago in my answer to your other question: Should I use EventArgs or a simple data type?, you could potentially end up in a situation where your code breaks.
Simplyfying at the maximum, when the framework invokes an OnClick event on a control, the .NET Framework does pass the sender and an
EventArgs
instance... if the event would not comply, something could break.if you want full freedom then use simple delegates but not events.
首先,需要注意的是,指南不是法律。
如果您不遵循指导方针,所有地狱(或程序员的等价物)都不会失败。
因此,请随意适当地更改事件的签名。
然而,了解为什么要添加这些指南也同样重要,而该问题的答案的一个重要部分是版本控制。
通过具有以下两个部分,并且仅这两个部分:
object
与当时一样通用)EventArgs
继承的对象,那么您正在设计对更改更具弹性的代码。
首先,由于您“不允许”添加或删除参数,因此您的事件的所有未来版本仍将只有
sender
和e
。其次,指南的第二部分是关于
e
参数的。如果您在新版本的类库中决定通过更改e
参数的类型来更改事件处理程序的签名,则应该使其更加具体 通过从当前类型下降,并传递后代。原因是已经处理当前(旧)类型的现有代码仍然可以工作。
因此,该指南背后的整个推理是:
现在,如果您的案例不关心其中任何一个问题,请随意不遵循该指南。
事实上,您可以从
Action
中创建一个事件,它会正常工作。First of all, it's important to note that a guideline is not a law.
All hell (or the programmer equivalent) will not break lose if you don't follow the guidelines.
As such, feel free to change the signature of your events appropriately.
However, it is just as important to know why these guidelines were added to begin with, and one big part of the answer(s) to that question is versioning.
By having the following two parts, and only those two parts:
object
is as generic as can be from back then)EventArgs
then you are designing code that is more resilient to changes.
First of all, since you're not "allowed" to add or remove parameters, all future versions of your event will still have only
sender
ande
.Secondly, there's a second part to the guideline regarding the
e
parameter. If you in a new version of your class library decides to change the signature of an event handler by changing the type of thee
parameter, you're supposed to make it more specific by descending from your current type, and passing the descendant instead.The reason for this is that existing code that already handles your current (old) type will still work.
So the entire reasoning behind the guideline is to:
Now, if any of this is not a concern for your case, feel free to not follow the guideline.
In fact, you can make an event out of an
Action
and it'll work just fine.-Chris Anderson,框架设计指南:可重用 .NET 库的约定、习惯用法和模式
如果您是一名 .NET 开发人员并且还没有读过这本书,那么您就错过了。它为您提供了一个了解 Microsoft .NET Framework 设计者思想的窗口,以及大量最佳实践(包括其背后的推理)。
(另外,您可以运行 FxCop 来验证这些做法是否得到遵守。)
-Chris Anderson, in Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries
If you're a .NET developer and you haven't read that book, you're missing out. It gives you a window ;) into the minds of the Microsoft .NET Framework designers, and a lot of best practices (including the reasoning behind them).
(Plus, you can run FxCop to verify that these practices are being followed.)
我认为该模式的原因是为了强制执行某种一致性。 sender 参数允许为多个发布者(按钮、表格)重复使用单个处理程序。
要解决您的问题:
1)根本不要使用它。这是很常见的,并且不会真正损害任何好的实践。
2) 没关系,再次忽略发件人
3) 与您在 2) 下所说的完全矛盾......
其余与1)相同。您甚至可以考虑将
null
作为发送者传递。4)“那为什么要包含它”-还有其他用例确实需要发送者。
但请注意,这只是图书馆确认 BCL 的指南。
您的案例听起来更像是一个特定的应用程序(而不是一个库),因此请随意使用您喜欢的任何参数方案。编译器不会抱怨。
I think the reason for the pattern is to enforce some consistency. The sender parameter allows re-use of a single handler for multiple publishers (buttons, tables).
To address your points:
1) simply don't use it. That is common and doesn't really hurt any good practice.
2) that's OK, again ignore the sender
3) is in total contradiction of what you said under 2) ...
And for the rest it is the same as 1). You could even consider passing
null
as sender.4) "then why include it" - there are other use cases that do require the sender.
But do note this is just a guideline for libraries confirming to the BCL.
Your case sounds more like a specific application (not a library) so feel free to use any parameter scheme you like. The compiler won't complain.
诸如此类的指导方针可以让活动的消费者具有可预测性。它还允许处理您在创建事件时可能从未考虑过的其他场景,特别是当您的库由第三方开发人员使用时。
它允许处理事件的方法始终拥有正确的对象实例以及有关事件触发原因的基本信息。
Guidelines such as this allow for predictability on the part of the consumer of the event. It also allows for handling of additional scenarios you may never have considered when you created the event, especially if your library is used by third party developers.
It allows the method handling the event to always have the correct object instance at hand as well as basic information regarding why the event was fired.
这只是一个很好的做法,但只要您不需要了解触发事件的对象或与该对象相关的任何其他信息,就可以了。我总是把它包括在内,因为你永远不知道什么时候会需要它。
我的建议是坚持下去,一点也不疼。
It's just good practice, but you'll be fine as long as you don't need to know about the object that fired the event or any other info related to the object. I for one always include it since you never know when you'll need it.
My suggestion is to stick with it, it does not hurt at all.
从语义上讲,如果事件处理程序对事件的来源感兴趣,则使用包含此类字段的
EventArgs
的派生类,这不会有什么问题。事实上,在很多情况下,这比将Sender
作为单独的参数传递要干净(例如,如果键盘快捷键处理器需要为按钮触发Click
处理程序,该事件实际上不应被视为由按钮引发,而是代表按钮引发)。不幸的是,如果将该信息合并到EventArgs
派生类型中,则每次引发事件时都必须创建EventArgs
派生类型的新实例,即使它否则可以使用EventArgs.Empty
。在这种常见情况下,使用单独的参数传递信息就无需让每个事件创建一个新的对象实例。尽管可能可以让关心事件来源的处理程序使用一个参数,而让那些不关心事件的处理程序省略该参数,但这需要任何辅助方法和类来协助处理事件订阅需要有包含或不包含参数的事件版本。让所有事件都采用两个参数,其中一个是
Object
类型,另一个是从EventArgs
派生的类型,这样就可以将一个辅助方法或类能够处理所有事件。There would have been nothing wrong, semantically, with having event handlers that are interested in where events came from use a derivative of
EventArgs
which included such a field. Indeed, there are many situations where that would be cleaner than passingSender
as a separate parameter (e.g. if a keyboard-shortcut processor needs to fire aClick
handler for a button, the event shouldn't really be considered to have been raised by the button, but rather raised on the button's behalf). Unfortunately, incorporating that information within anEventArgs
-derived type would make it necessary to create a new instance of anEventArgs
-derived type every time an event is raised, even if it could otherwise useEventArgs.Empty
. Using a separate parameter to pass the information eliminates the need to have every event create a new object instance in that common case.Although it might have been possible to have handlers that care about where an event came from use a parameter for that, and have those that don't care about it omit the parameter, that would have required that any helper methods and classes which assist with handling event subscriptions would need to have versions for events which did or did not include the parameter. Having all events take two parameters, one of which is of type
Object
and one of which is of a type derived fromEventArgs
makes it possible for one helper method or class to be capable of handling all events.