我已经阅读了至少十几篇关于事件的文章和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) { }
}
答案 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);
}
}
}
此示例中涉及的三个主要类是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()方法中,在检查事件为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
类型。您还需要少一个类型来维护,而不必回答“委托人是在这个文件中还是单独一个?”的问题。 (我的答案是,它是一个单独的一个;它是一个类似于任何其他的类型,并且应该得到它自己的工件,即使你可以在一行中写它。)