活动最佳实践:订阅和命名约定和选项

比萨

成员
已加入
2017年9月28日
留言内容
6
编程经验
5-10
各位,
我计划将Java库重写为C#。该库大量使用了自定义事件处理,即e。它包含许多实际上是可观察的类XYZ,XYZChangeEvent和由观察者实现的接口XYZChangeListener。通常,一个Observable同时是一个Observer,并将它从Oberservables接收到的更改事件传播到自己的Observer。
此事件处理将使用C#样式方式实现,即e。通过使用事件而不是接口来耦合观察者和可观察对象。
我已经阅读了该论坛中的各种主题(例如 http://www.sarahs-loft.com/showthread.php/3628-understanding-Delegates-and-Events )和其他内容,我认为我已经掌握了基本概念。但是,对于某些实现细节,我仍然不确定。
这是我作为原型编写的一组类,用于学习各种选项:
C#:
namespace EventStuff
{
    public static class EventHelper
    {
        public static void Raise<T>(this EventHandler<T> handler, Object sender, T e) where T: EventArgs{
            if(handler != null){
                handler(sender,e);
            }
        }
    }

    public class ItemChangeEvent: EventArgs
    {
        private Item source;
        
        private string message;
        
        public ItemChangeEvent(Item source, string message)
        {
            this.source = source;
            this.message = message;
        }
        public Item Source{
            get{return source;}
        }
        public string Message{
            get{return message;}
        }
    }
    
    public class Item
    {
        public event EventHandler<ItemChangeEvent> ItemChanged;
        
        private string label;
        
        public Item(string label)
        {
            this.label = label;
        }
        public string Label{
            get{return this.label;}
            set{
                this.label = value;
                OnItemChanged("Label changed");
                this.label = value;
            }
        }
        public void OnItemChanged(string message){
            ItemChanged.Raise(this, new ItemChangeEvent(this,message));
        }
    }
    
    public class ItemChangeListenerWithRef
    {
        private string name;
        
        private Item item;
        
        public ItemChangeListenerWithRef(string name, Item item)
        {
            this.name = name;
            this.item = item;
            item.ItemChanged+=this.ItemChanged;
        }
        public void ItemChanged(Object sender, ItemChangeEvent e){
            Console.WriteLine("I am '" + name + "' with an item reference. Change detected for item " +e.Source + " with message '" + e.Message+"'");
        }
    }

    public class ItemChangeListenerExcludingRef
    {
        private string name;
        

        public ItemChangeListenerExcludingRef(string name)
        {
            this.name = name;
        }
        public void ItemChanged(Object sender, ItemChangeEvent e){
            Console.WriteLine("I am '" + name + "'without an item reference. Change detected for item " +e.Source + " with message '" + e.Message+"'");
        }
    }
    public class ItemEventTest
    {
        public static void Main(string[] args)
        {
            Item first = new Item("My first item");
            ItemChangeListenerExcludingRef noRef = new ItemChangeListenerExcludingRef("First logger no ref");
            first.addItemChangeListener(noRef);
            ItemChangeListenerWithRef withRef = new ItemChangeListenerWithRef("First logger with ref", first);
            first.Listeners += new Item.Listener(noRef.itemChanged);
            first.Label = "My first changed item";
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }
}
一些问题:

显然,我不需要任何通过定义公共方法声明接口的接口。观察者只需要一种具有适当签名的方法。在上面的两个事件订阅者类中,相应的事件处理程序方法是公共的。但是,对于ItemChangeListenerWithRef实例,这不是必需的。 ItemChangeListenerWithRef实例仅订阅其引用的那些项目的事件,而不会被外部类用作订阅者。因此,可以使ItemChanged方法受保护甚至是私有的。是这样的方法"allowed",还是应该避免这种方法?

Item类的OnItemChanged方法获取字符串参数。我已经读过,这样的事件引发方法声明为具有从EventArgs派生的单个参数。这只是一项建议,更像是一项要求吗? OnItemChanged(string)方法的好处是它更短,因为EventArgs实例只需要在一个地方创建即可。在现实世界中,Item类将具有不止一个或几个可以更改的属性,并且其更改将不得不引发ChangeEvent。

我还阅读了关于忘记自定义事件的建议,仅阅读了实现事件通知机制的接口INotifyPropertyChanged。但是,此方法仅允许将事件的发送方(作为对象)和更改后的属性的名称传输给订户。使用自定义EventArgs提供了更多的选项来携带其他信息。自定义EventArgs的使用是否仍被认为是好的做法?我们不是在谈论Gui框架。

最后,事件,事件引发方法和观察者的命名是否正确?
感谢您的任何反馈!
 

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,559
地点
悉尼,澳大利亚
编程经验
10+
显然,我不需要任何通过定义公共方法声明接口的接口。观察者只需要一种具有适当签名的方法。在上面的两个事件订阅者类中,相应的事件处理程序方法是公共的。但是,对于ItemChangeListenerWithRef实例,这不是必需的。 ItemChangeListenerWithRef实例仅订阅其引用的那些项目的事件,而不会被外部类用作订阅者。因此,可以使ItemChanged方法受保护甚至是私有的。是这样的方法"allowed",还是应该避免这种方法?
引发事件的方法应声明为“ protected”,因为它永远不会直接从其类型外部进行调用,但很可能由派生类型调用。处理事件的方法应声明为“私有”,因为它将永远不会被直接调用,即使使用它自己的类型也是如此。
Item类的OnItemChanged方法获取字符串参数。我已经读过,这样的事件引发方法声明为具有从EventArgs派生的单个参数。这只是一项建议,更像是一项要求吗? OnItemChanged(string)方法的好处是它更短,因为EventArgs实例只需要在一个地方创建即可。在现实世界中,Item类将具有不止一个或几个可以更改的属性,并且其更改将不得不引发ChangeEvent。
这是一个约定,而不是一项要求,但您绝对应该遵守。如您所说,如果EventArgs实例是在事件引发方法内部创建的,则只需在一个地方创建它,但这实际上是一个弱点而不是强项。如上所述,引发事件的方法应声明为“受保护”,也应声明为“虚拟”。这样,派生类不仅可以引发事件,还可以向事件添加自定义功能。如果在引发事件的方法内创建EventArgs实例,则派生类将无法更改对象的创建方式,并且在基本方法返回后,它也将无法访问该对象。当使用CancelEventArgs或继承该类型的某种类型时,将需要进行此类访问的一个示例。您很可能需要在派生类的重写方法中测试该对象,以了解事件是否已取消。

事实是,在您认为这样做不会引起问题的情况下,您可以选择忽略该约定,但是我建议始终坚持这样做是一个好主意。这样,您将永远不会被您认为不会造成问题的情况所困扰,而事实证明确实如此,并且始终如一的代码总是更容易理解。
我还阅读了关于忘记自定义事件的建议,仅阅读了实现事件通知机制的接口INotifyPropertyChanged。但是,此方法仅允许将事件的发送方(作为对象)和更改后的属性的名称传输给订户。使用自定义EventArgs提供了更多的选项来携带其他信息。自定义EventArgs的使用是否仍被认为是好的做法?我们不是在谈论Gui框架。
我怀疑有人曾经建议完全避免自定义事件。人们可能会建议您仅优先实现INotifyPropertyChanged而不是添加大量的自定义事件,这些自定义事件仅表示特定属性已发生更改,而不是建议这样做。如果您需要做的还不止这些,那么您需要一个可以满足您需要的事件,并且应该继续执行。
最后,事件,事件引发方法和观察者的命名是否正确?
继承EventArgs的类的名称应始终以结尾"EventArgs",因此最好将ItemChangeEvent命名为ItemChangeEventArgs。

另外,如果您定义了一个专门由名为ItemChanged的事件使用的自定义EventArgs类型,则该类应命名为ItemChangedEventArgs,而不是ItemChangeEventArgs。名称应匹配以表明关系。

最后,如果您有一个名为Item的类,其属性名为Label,并且有一个事件表明该属性的值已更改,则该事件应命名为LabelChanged,而不是ItemChanged。

作为奖励,如果您要引发一个指示属性已更改的事件,则需要确保该属性实际上已更改。如果属性分配了当前值,则您不想引发该事件。这意味着在属性设置器中有一个“ if”语句。您可能想检查一下,看看是否还有其他有用的信息:

http://jmcilhinney.blogspot.com.au/2009/11/defining-and-raising-custom-events.html
 

比萨

成员
已加入
2017年9月28日
留言内容
6
编程经验
5-10
非常感谢您的回答。除了私有事件处理程序方法(即"ItemChanged"-方法,对吗?)。如果那是私有的,那么如何从类的外部对订阅过程进行编程,如上面对类ItemChangeListenerExclusionRef所述?还是这样的"外部创建的订阅" bad practice?
[编辑]我只是想了一点。将此方法声明为私有方法当然是有道理的,因为不应从外部调用它,也不应直接从类内部调用它。仅在发生事件时才应调用它。但是仍然应该可以从外部创建订阅。那么在诸如ItemChangeListenerExclusionRef之类的类中这样的公共方法呢?
C#:
public void CreateSubscription(Item item){
    item.ItemChanged+=this.ItemChanged;
}
 
Last edited:

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,559
地点
悉尼,澳大利亚
编程经验
10+
我从来没有遇到过这样的情况:一个对象需要将另一个对象注册为事件的处理程序,因此我从来不需要一个事件处理程序公开。如果您确实遇到了这种情况,我想该方法将需要公开。但是,这增加了类型之间的耦合,使处理事件的对象知道该事件将由其他类型注册。
 

比萨

成员
已加入
2017年9月28日
留言内容
6
编程经验
5-10
再次感谢您的回复。如果订阅者中的事件处理方法确实是私有的,则订阅者(上面示例中的ItemChangeListenerXYZ类的实例)需要获取对发布者的引用(Item类)才能订阅(公共)事件。我的问题是实现这一目标的首选方式是什么?我在这里找到一个简短的例子://docs.microsoft.com/en-us/do...ents-that-conform-to-net-framework-guidelines,其中订阅服务器在构造函数中获得对发布服务器的引用。但是,此方法不允许以编程方式释放订阅。
根据您的经验,编写不需要在整个生命周期内都订阅事件的订阅者的最佳方法是什么?我确实看到了两种基本方法:或者使用公共事件处理方法,或者使用两种公共方法来创建和释放订阅,其中将发布者作为方法参数提供。
 
最佳 底部