Помоги мне написать это событие Правильный путь - PullRequest
4 голосов
/ 17 июля 2010

Оригинальный вопрос

Я прочитал, по крайней мере, дюжину статей и ТАКИХ событий, и озадачил основные идеи, но я немного запутался в том, как это сделать. Правильный путь ™.Кажется, что есть по крайней мере два общих подхода к написанию событий, и один рекомендуется больше, чем другой.

Я вижу много материала, в котором автор пропускает части процесса, предполагая для некоторыхпричина того, что вы просто знаете это.Есть также много туториалов, таких как «MyEventExample» и «SomeProcessGoesHere», которые усложняют работу с примером в целом.Многие примеры приводят к тому, что вы учитесь тому, как что-то делать, только в конце заявляете, что вы, конечно, никогда не сделаете этого на самом деле - но тогда они не дадут того, как вы могли бы сделайте это.

Наконец, соглашения о присвоении имен для каждого из компонентов сценария, использующего события, по всей видимости, присутствуют на всей карте.У меня много проблем с выяснением того, где применяются определенные концепции, потому что каждый называет каждую его часть по-своему.

Итак, вот что я преследую: у меня есть простая ситуация в игре, которая использует события.Я бы хотел, чтобы кто-то пришел и написал о событиях, демонстрируя рекомендуемый подход и наиболее распространенные соглашения об именах и структурах.Я знаю, что это плохая форма, чтобы просить написать код для меня, но я действительно ищу способ , чтобы написать это, чтобы я мог начать уверенно делать это для себя.

Пожалуйстане обращайте внимания, является ли это хорошим игровым дизайном , или даже является ли это подходящей ситуацией для событий.Мне просто интересно, как правильно написать материал для события, и это мой пример.


//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 ]

3 голосов
/ 17 июля 2010

Вот пример типичного соглашения, используемого для настройки стандартных событий .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);
        }
    }
}


В этом примере задействованы три основных класса: Subject, Observer и TopicEventArgs. Класс Program служит только для предоставления драйвера для примера.

Сначала, глядя на метод Program.Main (), сначала создаются экземпляры Subject (объект, который будет вызывать события) и Observer (объект, который будет подписываться на вызванные события). Далее наблюдателю передается экземпляр субъекта, позволяющий ему подписаться на любые желаемые события. Наконец, вызывается метод SomeAction () субъекта, который приводит к возникновению события.

Глядя на тему, мы видим, что событие с именем TopicHappening типа EventHandler<TopicEventArgs> открыто объявлено. Тип EventHandler был введен в .Net 2.0 и позволяет объявлять события без необходимости явно определять типы делегатов. Класс Subject также имеет два метода, SomeAction() и OnTopicHappening(). Метод SomeAction() представляет точку в приложении, где Субъект выполняет какую-то задачу, о которой он хочет уведомить мир (то есть «любых наблюдателей»). Метод OnTopicHappening(TopicEventArgs) метод обеспечивает логическую точку в классе, где будет происходить событие. Во-первых, обратите внимание, что оно следует соглашению об именах On [Название события]. Хотя этот метод может называться как угодно, этот шаблон широко принят соглашением. Во-вторых, обратите внимание, что он определен для принятия одного аргумента типа TopicEventArgs. Это также следует стандартному соглашению и служит для принятия решения о том, какие аргументы события возникают в логической точке, в которой событие возникает (в методе SomeAction ()), а не из физической точки, в которой событие возникает. В-третьих, обратите внимание, что он объявлен защищенным виртуальным. Этот шаблон обычно используется для того, чтобы любые классы, расширяющие Subject, могли переопределить то, что именно происходит при возникновении события TopicHappening. В методе OnTopicHappening () событие TopicHappening назначается отдельной переменной до того, как событие проверяется на нулевое значение и вызывается. Это сделано для того, чтобы избежать возможного состояния гонки, при котором событие может быть очищено другим потоком (то есть всеми наблюдателями, отписавшимися) после проверки на нулевое значение, но до вызова события.

Глядя на класс TopicEventArgs, он представляет тему события, которую поднимает наша тема. Пользовательский класс EventArgs обычно создается, когда субъекту необходимо связать информацию с вызываемым событием. Для субъектов, которые хотят отправить только сигнальное событие без каких-либо связанных аргументов, следует использовать базовый класс EventArgs.Empty.

Наконец, класс Observer определяет объекты, которые будут получать уведомления о событиях от субъекта. В этом примере класс Observer предоставляет метод Observe () как способ получения ссылки на экземпляр Subject. Внутри метода частный метод обработчика событий с именем subject_TopicHappening назначается событию TopicHappening для субъекта. Этот формат имени является результатом делегата, автоматически генерируемого Visual Studio при вводе + = при регистрации для обработки события. Это по существу добавляет этот метод в коллекцию методов, вызываемых при возникновении события субъектом. При вызове закрытый метод subject_TopicHappening просто записывает сообщение, содержащееся в аргументах события, в консоль.

Надеюсь, это поможет.

2 голосов
/ 17 июля 2010

Событие - это, по сути, список методов.Чтобы добавить в этот список, сначала создайте метод с соответствующей подписью, затем используйте += в поле события объекта, который объявляет событие:

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прийти с событием.У вас также есть еще один тип для поддержки, и вам не нужно отвечать на вопрос «делегат входит в этот файл или в отдельный?»(Мой ответ заключается в том, что он входит в отдельный; это тип, подобный любому другому, и заслуживает своего собственного артефакта, даже если вы можете записать его в одну строку.)

...