帮我写这个事件正确的方法

时间:2010-07-17 15:11:39

标签: c# events conventions

原始问题

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

我看到了很多材料,其中作者跳过了部分过程,假设由于某种原因您只知道这一点。还有许多像“MyEventExample”和“SomeProcessGoesHere”这样的tutorialese,这使得整个例子变得更难。许多例子经历了教会你如何做某事的所有麻烦,只是在最后陈述你当然从未在现实中做过 - 但是他们没有提供你的方式< / 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) { }
}

2 个答案:

答案 0 :(得分:3)

以下是设置标准.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<TopicEventArgs>类型的事件。 EventHandler类型是在.Net 2.0中引入的,允许声明事件而无需显式定义委托类型。 Subject类还有两个方法SomeAction()OnTopicHappening()。方法SomeAction()表示应用程序中主题执行某些任务的点,它要通知世界(即“任何观察者”)。方法OnTopicHappening(TopicEventArgs)方法在类中提供将引发事件的逻辑点。首先,请注意它遵循命名约定On [事件的名称]。虽然这种方法可以任意命名,但这种模式已被大会广泛采用。其次,请注意它被定义为采用TopicEventArgs类型的单个参数。这也遵循标准约定,并且用于在事件被引发的逻辑点(在SomeAction()方法内)而不是从引发事件的物理点处保持决定什么事件参数。第三,注意它被声明为受保护的虚拟。通常遵循此模式以允许任何扩展Subject的类覆盖引发TopicHappening事件时发生的确切事件。 在OnTopicHappening()方法中,在检查事件为null并调用之前,将TopicHappening事件分配给单独的变量。这是为了避免可能的竞争条件,其中在检查null之后但在调用事件之前,事件可能被另一个线程(即所有观察者未订阅)清除。

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

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

希望这有帮助。

答案 1 :(得分:2)

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

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时,他们不必使用转到定义来查看事件带来的EventArgs类型。您还需要少一个类型来维护,而不必回答“委托人是在这个文件中还是单独一个?”的问题。 (我的答案是,它是一个单独的一个;它是一个类似于任何其他的类型,并且应该得到它自己的工件,即使你可以在一行中写它。)