帮我以正确的方式写这个活动

发布于 2024-09-10 04:17:37 字数 6459 浏览 6 评论 0原文

原始问题

我已经阅读了至少十几篇关于事件的文章和SO线程,并且对基本想法感到困惑,但我对如何以正确的方式做这件事有点困惑™。似乎至少有两种常见的事件编写方法,其中一种比另一种更值得推荐。

我看到很多材料,其中作者跳过了部分过程,假设出于某种原因您会知道这一点。还有很多像“MyEventExample”和“SomeProcessGoesHere”这样的教程,这使得理解整个示例变得更加困难。许多例子都经历了所有的麻烦来教你如何做某事,但最后却表明你在现实中当然永远不会这样做 - 但他们并没有提供你会<的方式/em> 去做吧。

最后,使用事件的场景的每个组件的命名约定似乎无处不在。我很难弄清楚某些概念的应用位置,因为每个人对它的每个部分的命名都不同。

这就是我所追求的:我在游戏中有一个简单的情况,需要利用事件。我希望有人进来编写事件接线,演示推荐的方法以及最常见的命名和结构约定。我知道要求为我编写代码的形式很糟糕,但我确实在寻找编写代码的方法,以便我可以自信地为自己编写代码。

请忽略这是否是一个好的游戏设计,甚至是否适合事件的情况。我只是感兴趣如何正确编写事件内容,这是我的示例空间。


//In my game I have a number of entities which can 'talk' to the player. 
//An NPC can greet them, a zone might 'greet' them by displaying "Cityville" 
//when they enter it, their inventory might tell them they're out of space, 
//and so on. Everything would pass a Message object to a Messenger object, 
//which would then deliver the messages as it saw fit.

public class Messenger
{
    private Queue<Message> _messages = new Queue<Message>();;
    private Stack<Message> _archive = new Stack<Message>();;

    public IEnumerable<Message> Messages { get { return _messages.AsEnumerable(); } }
    public IEnumerable<Message> Archive { get { return _archive.AsEnumerable(); } }

    public void Add(Message message)
    {
        _messages.Enqueue(message);
    }
    public void Deliver()
    {
        Message msg = _messages.Dequeue();
        _archive.Push(msg);

        //Here's where I'd broadcast to any subsystem listening 
        //that the message was delivered 
        //Event args should be (_archive.Peek(), DateTime.Now);
            
    }
        
    public event MessageDeliveryEvent Delivery;
    protected virtual void OnDelivery(MessageHandlerDeliveryEventArgs e)
    {
        if (this.Delivery != null) { this.Delivery(this, e); }
    }
}

//Okay, here's my delegate declared outside any class. One tutorial suggested 
//putting it in the same file as the event arguments class so it would be 
//easier to find, which sounds reasonable to me, but I dunno.

public delegate void MessageDeliveryEvent(object sender, MessageHandlerDeliveryEventArgs e);

//I've seen examples where they don't even write an event arguments class. 
//I think you could probably just pass the same stuff directly, but the 
//separate class sounds like a good idea, more flexible if things change.

public class MessageHandlerDeliveryEventArgs : EventArgs
{
    private readonly Message _message;
    private readonly DateTime _delivered;

    public MessageHandlerDeliveryEventArgs(Message message, DateTime delivered)
    {
        _message = message;
        _delivered = delivered;
    }

    public Message Message { get { return _message; } }
    public DateTime DeliveryDateTime { get { return _delivered; } }
}

//So in the UI layer I'd have things like a ChatBox which would be a
//scrolling list of everything said to the player. There would also be a 
//GeneralInfoBox control that displayed things like what zone you just 
//entered, or that your inventory is full. Both would listen to the 
//Messenger object for a delivery event, and then check the Message object 
//associated with that event to see if they needed to handle the display 
//of that message.

public class ChatBox
{
    //OMG there's a new message, lemme see if I should display it
    private void TheThingThatListensToMessengerEvents(Message message, DateTime datetime)
    {
        if Message.Type == MessageType.Chat { Print(datetime.ToString() + ": " + message.Text); }
    }
    public string Print(string text) {}
}
public class GeneralInfoBox
{
    //OMG there's a new message, lemme see if I should display it
    private void TheThingThatListensToMessengerEvents(Message message)
    {
        if Message.Type == MessageType.General { Print(message.Text); }
    }
    public string Print(string text) {}
}

如果我能澄清任何事情,请告诉我。如果我明显错过了一个非常好的教程,请随时向我指出。提前致谢。


我从线程中获取的内容

这是我的示例,其中包含事件。也许它会帮助那些像我一样思考的人(上帝帮助他们)想象它。


public class MessageHandler
{
    private Queue<Message> _messages = new Queue<Message>();
    private Stack<Message> _archive = new Stack<Message>();

    public MessageHandler() { }

    public IEnumerable<Message> Messages { get { return _messages.AsEnumerable(); } }
    public IEnumerable<Message> Archive { get { return _archive.AsEnumerable(); } }

    public void Add(Message message)
    {
        _messages.Enqueue(message);
    }
    public void Deliver()
    {
        Message msg = _messages.Dequeue();
        _archive.Push(msg);

        //Call the method which broadcasts the event
        OnDelivery(new MessageDeliveryEventArgs(_archive.Peek(), DateTime.Now));
    }

    //The event
    public event EventHandler<MessageDeliveryEventArgs> Delivery;

    //The method which broadcasts the event
    protected virtual void OnDelivery(MessageDeliveryEventArgs messageDeliveryEventArgs)
    {
        EventHandler<MessageDeliveryEventArgs> handler = Delivery;
        if (handler != null) { handler(this, messageDeliveryEventArgs); }
    }
}

//The event arguments class for the event of interest. Carries information about this kind of event
public class MessageDeliveryEventArgs : EventArgs
{
    private readonly Message _message;
    private readonly DateTime _delivered;

    public MessageDeliveryEventArgs(Message message, DateTime delivered)
    {
        _message = message;
        _delivered = delivered;
    }

    public Message Message { get { return _message; } }
    public DateTime DeliveryDateTime { get { return _delivered; } }
}

//A UI control which listens for an event in a Messenger object
public class ChatBox
{
    //Specify the instance of the Messenger class to whose event(s) we plan to subscribe
    public ChatBox(MessageHandler messenger)
    {
        //Subscribe this control's OnDelivery method to the Delivery event of the specified instance of Messenger
        messenger.Delivery += this.OnDelivery;
    }

    //The method which we intend to subscribe to the Delivery event of an instance of Messenger
    private void OnDelivery(object sender, MessageDeliveryEventArgs e)
    {
        if (e.Message.Format == MessageFormat.Local)
        {
            Print(String.Format("{0}: {1}", e.DeliveryDateTime, e.Message.Text));
        }
    }
    private void Print(string text) { }
}

Original Question

I've read at least a dozen articles and SO threads on events and have puzzled out the basic ideas, but I'm a bit muddy on how to do it The Right Way™. It seems that there are at least two common approaches to writing events, and one is more recommended than the other.

I'm seeing a lot of material in which the author skips parts of the process, assuming for some reason that you would just know that. There's also a lot of tutorialese like "MyEventExample" and "SomeProcessGoesHere" which makes it harder to grok the example as a whole. Many examples go through all the trouble of teaching you how to do something, only to state at the end of it that you'd of course never do that in reality - but then they don't provide the way you would do it.

Finally, the naming conventions for each of the components of a scenario using events seem to be all over the map. I have a lot of trouble figuring out where certain concepts are being applied because everyone names every part of it differently.

So here's what I'm after: I have a simple situation in a game which would make use of events. I'd like someone to come in and write the event wireup, demonstrating the recommended approach and the most common naming and structural conventions. I know it's poor form to ask for code to be written for me, but I'm really looking for the way to write it so I can start confidently doing it for myself.

Please ignore whether this is a good game design, or even whether it's an appropriate situation for events. I'm just interested how to properly write the event stuff, and this is my example space.


//In my game I have a number of entities which can 'talk' to the player. 
//An NPC can greet them, a zone might 'greet' them by displaying "Cityville" 
//when they enter it, their inventory might tell them they're out of space, 
//and so on. Everything would pass a Message object to a Messenger object, 
//which would then deliver the messages as it saw fit.

public class Messenger
{
    private Queue<Message> _messages = new Queue<Message>();;
    private Stack<Message> _archive = new Stack<Message>();;

    public IEnumerable<Message> Messages { get { return _messages.AsEnumerable(); } }
    public IEnumerable<Message> Archive { get { return _archive.AsEnumerable(); } }

    public void Add(Message message)
    {
        _messages.Enqueue(message);
    }
    public void Deliver()
    {
        Message msg = _messages.Dequeue();
        _archive.Push(msg);

        //Here's where I'd broadcast to any subsystem listening 
        //that the message was delivered 
        //Event args should be (_archive.Peek(), DateTime.Now);
            
    }
        
    public event MessageDeliveryEvent Delivery;
    protected virtual void OnDelivery(MessageHandlerDeliveryEventArgs e)
    {
        if (this.Delivery != null) { this.Delivery(this, e); }
    }
}

//Okay, here's my delegate declared outside any class. One tutorial suggested 
//putting it in the same file as the event arguments class so it would be 
//easier to find, which sounds reasonable to me, but I dunno.

public delegate void MessageDeliveryEvent(object sender, MessageHandlerDeliveryEventArgs e);

//I've seen examples where they don't even write an event arguments class. 
//I think you could probably just pass the same stuff directly, but the 
//separate class sounds like a good idea, more flexible if things change.

public class MessageHandlerDeliveryEventArgs : EventArgs
{
    private readonly Message _message;
    private readonly DateTime _delivered;

    public MessageHandlerDeliveryEventArgs(Message message, DateTime delivered)
    {
        _message = message;
        _delivered = delivered;
    }

    public Message Message { get { return _message; } }
    public DateTime DeliveryDateTime { get { return _delivered; } }
}

//So in the UI layer I'd have things like a ChatBox which would be a
//scrolling list of everything said to the player. There would also be a 
//GeneralInfoBox control that displayed things like what zone you just 
//entered, or that your inventory is full. Both would listen to the 
//Messenger object for a delivery event, and then check the Message object 
//associated with that event to see if they needed to handle the display 
//of that message.

public class ChatBox
{
    //OMG there's a new message, lemme see if I should display it
    private void TheThingThatListensToMessengerEvents(Message message, DateTime datetime)
    {
        if Message.Type == MessageType.Chat { Print(datetime.ToString() + ": " + message.Text); }
    }
    public string Print(string text) {}
}
public class GeneralInfoBox
{
    //OMG there's a new message, lemme see if I should display it
    private void TheThingThatListensToMessengerEvents(Message message)
    {
        if Message.Type == MessageType.General { Print(message.Text); }
    }
    public string Print(string text) {}
}

If I can clarify anything, let me know. If there's a really good tutorial I've obviously missed, please feel free to just point me to that. Thanks in advance.


What I've taken from the thread

So here's my example with events wired into it. Maybe it will help someone else who thinks like me (God help them) visualize it.


public class MessageHandler
{
    private Queue<Message> _messages = new Queue<Message>();
    private Stack<Message> _archive = new Stack<Message>();

    public MessageHandler() { }

    public IEnumerable<Message> Messages { get { return _messages.AsEnumerable(); } }
    public IEnumerable<Message> Archive { get { return _archive.AsEnumerable(); } }

    public void Add(Message message)
    {
        _messages.Enqueue(message);
    }
    public void Deliver()
    {
        Message msg = _messages.Dequeue();
        _archive.Push(msg);

        //Call the method which broadcasts the event
        OnDelivery(new MessageDeliveryEventArgs(_archive.Peek(), DateTime.Now));
    }

    //The event
    public event EventHandler<MessageDeliveryEventArgs> Delivery;

    //The method which broadcasts the event
    protected virtual void OnDelivery(MessageDeliveryEventArgs messageDeliveryEventArgs)
    {
        EventHandler<MessageDeliveryEventArgs> handler = Delivery;
        if (handler != null) { handler(this, messageDeliveryEventArgs); }
    }
}

//The event arguments class for the event of interest. Carries information about this kind of event
public class MessageDeliveryEventArgs : EventArgs
{
    private readonly Message _message;
    private readonly DateTime _delivered;

    public MessageDeliveryEventArgs(Message message, DateTime delivered)
    {
        _message = message;
        _delivered = delivered;
    }

    public Message Message { get { return _message; } }
    public DateTime DeliveryDateTime { get { return _delivered; } }
}

//A UI control which listens for an event in a Messenger object
public class ChatBox
{
    //Specify the instance of the Messenger class to whose event(s) we plan to subscribe
    public ChatBox(MessageHandler messenger)
    {
        //Subscribe this control's OnDelivery method to the Delivery event of the specified instance of Messenger
        messenger.Delivery += this.OnDelivery;
    }

    //The method which we intend to subscribe to the Delivery event of an instance of Messenger
    private void OnDelivery(object sender, MessageDeliveryEventArgs e)
    {
        if (e.Message.Format == MessageFormat.Local)
        {
            Print(String.Format("{0}: {1}", e.DeliveryDateTime, e.Message.Text));
        }
    }
    private void Print(string text) { }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

雨夜星沙 2024-09-17 04:17:37

下面是设置标准 .Net 事件时遵循的典型约定的示例:

using System;

namespace ObserverExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var subject = new Subject();
            var observer = new Observer();
            observer.Observe(subject);
            subject.SomeAction();
            Console.ReadLine();
        }
    }  

    public class Subject
    {
        public event EventHandler<TopicEventArgs> TopicHappening;

        public void SomeAction()
        {
            OnTopicHappening(new TopicEventArgs("Hello, observers!"));
        }

        protected virtual void OnTopicHappening(TopicEventArgs topicEventArgs)
        {
            EventHandler<TopicEventArgs> handler = TopicHappening;

            if (handler != null)
                handler(this, topicEventArgs);
        }
    }

    public class TopicEventArgs : EventArgs
    {
        public TopicEventArgs(string message)
        {
            Message = message;
        }

        public string Message { get; private set; }
    }

    public class Observer
    {
        public void Observe(Subject subject)
        {
            subject.TopicHappening += subject_TopicHappening;
        }

        void subject_TopicHappening(object sender, TopicEventArgs e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

此示例中涉及的三个主要类是 SubjectObserverTopicEventArgsProgram 类仅用于为示例提供驱动程序。

首先查看 Program.Main() 方法,首先实例化Subject(将引发事件的对象)和Observer(将订阅引发的事件的对象)的实例。接下来,向观察者传递主题的实例,使其有机会订阅任何所需的事件。最后,调用主体的 SomeAction() 方法,从而引发事件。

查看主题,我们看到一个名为 TopicHappening 且类型为 EventHandler 的事件被公开声明。 EventHandler 类型是在 .Net 2.0 中引入的,它允许声明事件而无需显式定义委托类型。 subject 类还有两个方法:SomeAction()OnTopicHappening()。方法 SomeAction() 表示应用程序中主体执行某些任务并通知世界(即“任何观察者”)的点。 OnTopicHappening(TopicEventArgs) 方法提供了类中将引发事件的逻辑点。首先,请注意它遵循 On[事件名称] 的命名约定。虽然这种方法可以被命名为任何名称,但这种模式已按照惯例被广泛采用。其次,请注意它被定义为采用 TopicEventArgs 类型的单个参数。这也遵循标准约定,并用于在引发事件的逻辑点(在 SomeAction() 方法内)而不是从引发事件的物理点来决定引发哪些事件参数。第三,请注意它被声明为 protected virtual。通常遵循此模式是为了允许扩展主题的任何类覆盖引发 TopicHappening 事件时实际发生的情况。
在 OnTopicHappening() 方法中,在检查事件是否为 null 并调用之前,将 TopicHappening 事件分配给单独的变量。这是为了避免可能出现的竞争情况,即在检查空值之后但在调用事件之前,事件可能会被另一个线程(即所有观察者取消订阅)清除。

查看 TopicEventArgs 类,它代表我们的主题提出的事件主题。当主题需要将信息与引发的事件相关联时,通常会创建自定义 EventArgs 类。对于只希望发送没有任何关联参数的信号事件的主体,应使用基本 EventArgs.Empty 类。

最后,Observer 类定义将从Subject 接收事件通知的对象。在此示例中,Observer 类公开 Observe() 方法,作为接收对Subject 实例的引用的方法。在该方法中,名为 subject_TopicHappening 的私有事件处理程序方法被分配给主题上的 TopicHappening 事件。此名称格式是 Visual Studio 在注册处理事件时键入 += 时自动生成的委托的结果。这实质上是将此方法添加到方法集合中,以便在主题引发事件时调用。调用时,私有 subject_TopicHappening 方法只是将事件参数中包含的消息写入控制台。

希望这有帮助。

Here is an example of a typical convention followed for setting up standard .Net events:

using System;

namespace ObserverExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var subject = new Subject();
            var observer = new Observer();
            observer.Observe(subject);
            subject.SomeAction();
            Console.ReadLine();
        }
    }  

    public class Subject
    {
        public event EventHandler<TopicEventArgs> TopicHappening;

        public void SomeAction()
        {
            OnTopicHappening(new TopicEventArgs("Hello, observers!"));
        }

        protected virtual void OnTopicHappening(TopicEventArgs topicEventArgs)
        {
            EventHandler<TopicEventArgs> handler = TopicHappening;

            if (handler != null)
                handler(this, topicEventArgs);
        }
    }

    public class TopicEventArgs : EventArgs
    {
        public TopicEventArgs(string message)
        {
            Message = message;
        }

        public string Message { get; private set; }
    }

    public class Observer
    {
        public void Observe(Subject subject)
        {
            subject.TopicHappening += subject_TopicHappening;
        }

        void subject_TopicHappening(object sender, TopicEventArgs e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

The three primary classes involved in this example are the Subject, Observer, and the TopicEventArgs. The Program class serves only to provide a driver for the example.

Looking first at the Program.Main() method, instances of Subject (the object that will be raising events) and Observer (the object that will be subscribing to the raised events) are first instantiated. Next, the observer is passed an instance of the subject allowing it the opportunity to subscribe to any desired events. Lastly, the subject's SomeAction() method is called which results in the raising of the event.

Looking at the Subject, we see that an event named TopicHappening of type EventHandler<TopicEventArgs> is publicly declared. The EventHandler type was introduced in .Net 2.0 and allows events to be declared without having to explicitly define delegate types. The Subject class also has two methods, SomeAction() and OnTopicHappening(). The method SomeAction() represents the point within the application where the Subject performs some task for which it wants to notify the world (i.e. "any observers") about. The method OnTopicHappening(TopicEventArgs) method provides the logical point within the class where the event will be raised. First, notice that it follows the naming convention On[Name of the event]. While this method can be named anything, this pattern has been widely adopted by convention. Second, notice that it is defined to take a single argument of type TopicEventArgs. This also follows a standard convention and serves the purpose of keeping the decision of what event arguments are raise at the logical point the event is raised (within the SomeAction() method) rather than from the physical point where the event is raised. Third, notice that it is declared protected virtual. This pattern is generally followed to allow any classes extending Subject to override what exactly happens when the TopicHappening event is raised.
Within the OnTopicHappening() method, the TopicHappening event is assigned to a separate variable before the event is checked for null and invoked. This is to avoid a possible race condition where the event may be cleared by another thread (i.e. all observers unsubscribed) after the check for null, but before the event is invoked.

Looking at the TopicEventArgs class, this represents the event topic our subject raises. A custom EventArgs class is generally created when the subject needs to associate information with the event being raised. For subjects which only wish to send a signal event without any associated arguments, the base EventArgs.Empty class should be used.

Lastly, the Observer class defines the objects which will receive the event notifications from the Subject. In this example, the Observer class exposes an Observe() method just as a way to receive a reference to an instance of Subject. Within the method, a private event handler method named subject_TopicHappening is assigned to the TopicHappening event on the subject. This name format is the result of the delegate automatically generated by Visual Studio upon typing += when registering to handle the event. This essentially adds this method to the collection of methods to invoke when the event is raised by the subject. When invoked, the private subject_TopicHappening method simply writes the message contained within the event arguments out to the Console.

Hope this helps.

千寻… 2024-09-17 04:17:37

事件基本上是方法列表。要添加到该列表,首先创建一个具有匹配签名的方法,然后在声明事件的对象的事件字段上使用 +=

public class ChatBox
{
    public ChatBox(Messenger messenger)
    {
        messenger.Delivery += OnMessageDelivery;
    }

    private void OnMessageDelivery(object sender, MessageHandlerDeliveryEventArgs e)
    {
        if(e.Message.Type == MessageType.Chat)
        {
            Print(String.Format("{0}: {1}", e.DateTime, e.Message.Text));
        }
    }
}

+= 包装委托中的方法并将其附加到由 Delivery 表示的现有委托列表中。该处理程序现在与 Messenger特定实例上的事件相关联。

此外,您不需要使用自定义委托类型。您可以这样声明事件:

public event EventHandler<MessageHandlerDeliveryEventArgs> Delivery;

当有人浏览这种风格的 API 时,他们不必使用 Go To Definition 来查看该事件附带什么样的 EventArgs事件。您还可以少维护一种类型,并且不必回答“委托是放入此文件还是单独的文件中?”这一问题。 (我的答案是,它放在一个单独的类型中;它是一种像任何其他类型一样的类型,值得拥有自己的工件,即使您可以将其写在一行中。)

An event is basically a list of methods. To add to that list, first create a method which has the matching signature, then use += on the event field of the object which declares the event:

public class ChatBox
{
    public ChatBox(Messenger messenger)
    {
        messenger.Delivery += OnMessageDelivery;
    }

    private void OnMessageDelivery(object sender, MessageHandlerDeliveryEventArgs e)
    {
        if(e.Message.Type == MessageType.Chat)
        {
            Print(String.Format("{0}: {1}", e.DateTime, e.Message.Text));
        }
    }
}

The += wraps the method in a delegate and appends it to the existing list of delegates represented by Delivery. The handler is now associated with the event on that particular instance of Messenger.

Also, you don't need to use a custom delegate type. You can declare the event like this:

public event EventHandler<MessageHandlerDeliveryEventArgs> Delivery;

When someone is browing this style of API, they don't have to use Go To Definition to see what kind of EventArgs come with the event. You also have one less type to maintain and don't have to answer the question "does the delegate go in this file or a separate one?" (My answer is that it goes in a separate one; it is a type like any other and deserves its own artifact, even though you can write it in one line.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文