I'm building a game and trying to use an event system.
This is the main idea of how it is implemented:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Blocker2
{
public delegate void OnPlayerMoved(PlayerMovedEvent e);
public delegate void OnPlayerSpawned(PlayerSpawnedEvent e);
public delegate void OnBlockBreak(BlockBreakEvent e);
public delegate void OnBlockPlaced(BlockPlacedEvent e);
public static class EventHandler
{
private static List<OnPlayerMoved> _onPlayerMoved;
private static List<OnPlayerSpawned> _onPlayerSpawned;
private static List<OnBlockBreak> _onBlockBreak;
private static List<OnBlockPlaced> _onBlockPlaced;
static EventHandler()
{
}
public static void Subscribe()
{
}
// -------------------------- Player Related Events --------------------------
public static void OnPlayerMoved(PlayerMovedEvent e)
{
foreach (OnPlayerMoved del in _onPlayerMoved)
{
del(e);
}
}
public static void OnPlayerSpawned(PlayerSpawnedEvent e)
{
foreach (OnPlayerSpawned del in _onPlayerSpawned)
{
del(e);
}
}
// -------------------------- Block Related Events --------------------------
public static void OnBlockBreak(BlockBreakEvent e)
{
foreach (OnBlockBreak del in _onBlockBreak)
{
del(e);
}
}
public static void OnBlockPlaced(BlockPlacedEvent e)
{
foreach (OnBlockPlaced del in _onBlockPlaced)
{
del(e);
}
}
}
}
And there going to be alot more events, and I think this method going to make the code very very complex. There is a better way to do it? (Considering performance and maintability of the code).
Thanks in advanced!
Sorry for my bad english.
Why don't you use standard C# events? They will handle this the same way, since an event allows more than a single subscriber.
The standard event mechanism in C# allows multiple subscribers to subscribe to an event, and would look like:
public static event EventHandler<PlayerMovedEventArgs> PlayerMoved;
// Often, you'll have a method to raise the event:
public static void OnPlayerMoved(PlayerMovedEventArgs args)
{
var handler = PlayerMoved;
if (handler != null)
handler(null, args);
}
That being said, I would recommend putting these into the class where they are related, and not having them all global/static. You could then potentially make the method to raise the event private to that class, which would allow you to keep the design more maintainable.
For example, the PlayerMoved event probably would be more appropriate within some class representing your world (or a piece of the world), and in there in a non-static fashion.
Related
I want to send an alert message to any subscribers when a trap occurred.
The code I created works fine using a delegate method myDelegate del.
My questions are:
I want to know whether it's better to use EventHandler instead of a delegate?
I'm not sure what the differences are between a delegate and an EventHandler in my case.
notify(trapinfo t), that's what I've done here to get trap information. But it seems not to be a good idea. I read some online tutorial lesson introducing passing delegate object; I'm wondering if it's appropriate in my case? And how should I do it? Any suggestions?
Thanks a lot :)
My code:
public class trapinfo
{
public string info;
public string ip;
public string cause;
}
public class trap
{
public delegate void myDelegate(trapinfo t);
public myDelegate del;
trapinfo info = new trapinfo();
public void run()
{
//While(true)
// If a trap occurred, notify the subscriber
for (; ; )
{
Thread.Sleep(500);
foreach (myDelegate d in del.GetInvocationList())
{
info.cause = "Shut Down";
info.ip = "192.168.0.1";
info.info = "Test";
d.Invoke(info);
}
}
}
}
public class machine
{
private int _occuredtime=0;
public trapinfo info = new trapinfo();
public void notify(trapinfo t)
{
++_occuredtime;
info.cause = t.cause;
info.info = t.info;
info.ip = t.ip;
getInfo();
}
public void subscribe(trap t)
{
t.del += new trap.myDelegate(notify);
}
public void getInfo()
{
Console.WriteLine("<Alert>: cauese/{0}, info/ {1}, ip/{2}, time/{3}",
info.cause, info.info, info.ip,_occuredtime);
}
}
class Program
{
static void Main(string[] args)
{
trap t = new trap();
machine machineA = new machine();
machineA.subscribe(t);
t.run();
}
}
Update 2013-08-12
How about the observer/observable design pattern, that looks great in my case (EventHandler).
In my case, a machine subscribes to a trap messenger. (Add a machine to an invocation list)
Once a trap occurred, I send a message to all machines which are subscribed. (Call HandleEvent to handle it)
Advantages:
don't care about GetInvocationList() anymore, just use (+=) and (-=) to decide whom to send the trap.
It's easier to understand the logic of my program.
I know there are several ways to do it, but I wish I could analyze its pros and cons.
And thanks for your comments and suggestions, that would be very helpful!
I read the MSDN EventArgs article which Matthew Watson suggested.
Here's my Event Version:
public class TrapInfoEventArgs : EventArgs
{
public int info { get; set; }
public string ip { get; set; }
public string cause { get; set; }
}
public class trap
{
public event EventHandler<TrapInfoEventArgs> TrapOccurred;
protected virtual void OnTrapOccurred(TrapInfoEventArgs e)
{
EventHandler<TrapInfoEventArgs> handler = TrapOccurred;
if (handler != null)
{
handler(this, e);
}
}
public void run()
{
//While(true)
// If a trap occurred, notify the subscriber
for (; ; )
{
Thread.Sleep(500);
TrapInfoEventArgs args = new TrapInfoEventArgs();
args.cause = "Shut Down";
OnTrapOccurred(args);
}
}
}
public class machine
{
public void c_TrapOccurred(object sender, TrapInfoEventArgs e)
{
Console.WriteLine("<Alert>: cauese/{0}, info/ {1}, ip/{2}, time/{3}",
e.cause, e.info, e.ip, DateTime.Now.ToString());
}
}
class Program
{
static void Main(string[] args)
{
trap t = new trap();
machine machineA = new machine();
t.TrapOccurred += machineA.c_TrapOccurred; //notify machine A
t.run();
}
}
The difference between event and delegate is that:
event declaration adds a layer of protection on the delegate instance.
This protection prevents clients of the delegate from resetting the
delegate and its invocation list, and only allows adding or removing
targets from the invocation list
See What are the differences between delegates and events?
2) As I see it, your subscriber should not change delegates freely. One subscriber can assign = to it instead of adding +=. This will assign a new delegate, therefore, the previous delegate with its invocation list will be lost and previous subscribers will not be called anymore. So you should use Event for sure. Or you can change your code to make your delegate private and write additional functions for manipulating it to define your own event behavior.
//preventing direct assignment
private myDelegate del ;
public void AddCallback(myDelegate m){
del += m;
}
public void RemoveCallback(myDelegate m){
del -= m;
}
//or
public static trap operator +(trap x,myDelegate m){
x.AddCallback(m);
return x;
}
public static trap operator -(trap x, myDelegate m)
{
x.RemoveCallback(m);
return x;
}
//usage
//t.AddCallback(new trap.myDelegate(notify));
t+=new trap.myDelegate(notify);
It is much better to use an event for your example.
An event is understood by the Visual Studio Form and WPF designers, so you can use the IDE to subscribe to events.
When raising events, there is no need for you to write your own foreach handling to iterate through them.
events are the way that most programmers will expect this functionality to be accessed.
If you use a delegate, the consuming code can mess around with it in ways that you will want to prevent (such as resetting its invocation list). events do not allow that to happen.
As for your second question: Using an event you would create a class derived from EventArgs to hold the data, and pass that to the event when you raise it. The consumer will then have access to it.
See here for details: http://msdn.microsoft.com/en-us/library/system.eventargs.aspx
I'm trying to create a reusable method for a more complicated event execution. I can't get it to compile or run with framework events that don't follow the EventHandler<Type> pattern. I would like to avoid reflection if possible as it will be a heavily used event.
I've created a test console app below which illustrates the problem:
using System;
using System.Collections.Specialized;
namespace CallEventsViaMethod
{
public class TestEventArgs : EventArgs { }
class Program
{
static void Main(string[] args)
{
MyProgram program = new MyProgram();
program.Go();
Console.ReadKey(false);
}
}
public class MyProgram
{
public event EventHandler<TestEventArgs> TestEvent;
public event NotifyCollectionChangedEventHandler CollectionChangedEvent;
public void Go()
{
TestEvent += new EventHandler<TestEventArgs>(MyProgram_TestEvent);
CollectionChangedEvent += new NotifyCollectionChangedEventHandler(MyProgram_CollectionChangedEvent);
// Want a reusable method I can use to conditionally execute any event
GeneralEventExecutor.Execute<TestEventArgs>(TestEvent, new Object(), new TestEventArgs());
GeneralEventExecutor.Execute<NotifyCollectionChangedEventArgs>(TestEvent, new Object(), new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
void MyProgram_TestEvent(object arg1, TestEventArgs arg2)
{
Console.WriteLine("Custom event ran");
}
void MyProgram_CollectionChangedEvent(object sender, NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("NotifyCollectionChangedEventHandler event ran");
}
}
public static class GeneralEventExecutor
{
public static void Execute<T>(EventHandler<T> eventToRaise, object sender, T eventArgs) where T : EventArgs
{
if (eventToRaise == null)
return;
Delegate[] registeredEventHandlers = eventToRaise.GetInvocationList();
foreach (EventHandler<T> eventHandler in registeredEventHandlers)
{
object target = eventHandler.Target; // Need access to the Target property
// * Code deciding if should invoke the event handler *
eventHandler.Invoke(sender, eventArgs);
}
}
}
}
Error messages are:
error CS1502: The best overloaded method match for
'CallEventsViaMethod.GeneralEventExecutor.Execute(System.EventHandler,
object,
System.Collections.Specialized.NotifyCollectionChangedEventArgs)' has
some invalid arguments
error CS1503: Argument 1: cannot convert from
'System.EventHandler' to
'System.EventHandler'
I understand why I'm getting the error, but can't figure out a way round it.
Replace your your generic Execute<T> as below
public static void Execute<T>(Delegate eventToRaise, object sender, T eventArgs) where T:EventArgs
{
if (eventToRaise == null)
return;
Delegate[] registeredEventHandlers = eventToRaise.GetInvocationList();
foreach (Delegate eventHandler in registeredEventHandlers)
{
object target = eventHandler.Target; // Need access to the Target property for conditions
// * Code deciding if should invoke the event handler *
eventHandler.DynamicInvoke(sender, eventArgs);
}
}
I want to use event itself in my method. Is it possible?
Can "PlayWithEvent" method use "EventSource.Test" event as parameter?
public class EventSource
{
public event EventHandler Test;
}
class Program
{
static void Main(string[] args)
{
EventSource src = new EventSource ();
PlayWithEvent (src.Test);
}
static void PlayWithEvent (EventHandler e)
{
e (null, null);
}
}
I want a syntax something like that:
class Program
{
static void Main(string[] args)
{
EventSource src = new EventSource ();
PlayWithEvent (src.Test);
}
static void PlayWithEvent (event e)
{
e += something;
}
}
Your code won't compile—you can only access the EventHandler delegate for the event from within the same class as the event, and even then it would be null unless you actually add an event handler to call.
It is not currently working because you marked
public event EventHandler Test;
as an event.
Remove the event tag and try again. It now works for me. The reason for this are the restrictions that C# has for events... But in your code, all you want is a delegate. Declare your class as:
public class EventSource
{
public EventHandler Test;
}
Note that I removed event:
public EventHandler Test;
You can't do it like that. You need to pass the actual class.
public class EventSource
{
public event EventHandler Test;
public void TriggerEvent()
{
Test(this, EventArgs.Empty);
}
}
class Program
{
static void Main(string[] args)
{
EventSource src = new EventSource ();
PlayWithEvent (src);
}
static void PlayWithEvent (EventSource e)
{
src.TriggerEvent();
}
}
You can do it in a more generic way by introducing an interface:
public interface IEventPublisher<T> where T : EventArgs
{
public void Publish(T args);
}
public class EventSource : IEventPublisher<EventArgs>
{
public event EventHandler Test;
public void Publish(EventArgs args)
{
Test(this, args);
}
}
class Program
{
static void Main(string[] args)
{
EventSource src = new EventSource ();
PlayWithEvent (src);
}
static void PlayWithEvent (IEventPublisher<EventArgs> publisher)
{
publisher.Publish(EventArgs.Empty);
}
}
Only the class that an event is defined on can raise the event. If you need other classes to be able to manipulate it, you'll have to use a regular delegate.
Note, however, that since Test resolves to the underlying delegate when used within the scope of EventSource (as opposed to the externally accessible event), EventSource can pass it as a parameter to an external method.
How would you suggest the best way of avoiding duplicate event subscriptions? if this line of code executes in two places, the event will get ran twice. I'm trying to avoid 3rd party events from subscribing twice.
theOBject.TheEvent += RunMyCode;
In my delegate setter, I can effectively run this ...
theOBject.TheEvent -= RunMyCode;
theOBject.TheEvent += RunMyCode;
but is that the best way?
I think, the most efficient way, is to make your event a property and add concurrency locks to it as in this Example:
private EventHandler _theEvent;
private object _eventLock = new object();
public event EventHandler TheEvent
{
add
{
lock (_eventLock)
{
_theEvent -= value;
_theEvent += value;
}
}
remove
{
lock (_eventLock)
{
_theEvent -= value;
}
}
}
I have done this before....it assumes it is acceptable that the last subscriber is what gets called.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MyObject my = new MyObject();
my.Changed += new EventHandler(my_Changed);
my.Changed += new EventHandler(my_Changed1);
my.Update();
Console.ReadLine();
}
static void my_Changed(object sender, EventArgs e)
{
Console.WriteLine("Hello");
}
static void my_Changed1(object sender, EventArgs e)
{
Console.WriteLine("Hello1");
}
}
public class MyObject
{
public MyObject()
{
}
private EventHandler ChangedEventHandler;
public event EventHandler Changed
{
add
{
ChangedEventHandler = value;
}
remove
{
ChangedEventHandler -= value;
}
}
public void Update()
{
OnChanged();
}
private void OnChanged()
{
if (ChangedEventHandler != null)
{
ChangedEventHandler(this, null);
}
}
}
}
Is your code multi threaded ? Concurrency lock is needed only when its multi threaded. If not its a overhead.
As such your approach of unsubscribing and subscribing is correct.
Thanks
If you own the source for the class of theObject, then you have access to the InvocationList of TheEvent. You can implement your own add accessor for the event and check before adding.
However, I think that your approach is fine too.
I use your approach except one detail. I think, that events should be subscribed when you create new instance of subscriber or theObject, this makes code more straight. Thus, all you need is just carefully watch after correct objects disposing (dispose patten is convenient solution for this).
You mentioned that you use 3rd party event, that means that you can't provide your own realisation for add/remove methods, as you have been advised. But in your own classes with your own events you should define your own realisation of add/remove methods for event in order to solve your problem.
Any efficient/reliable way to expose one event?
I have a class, MultipleDocumentCopier that copies multiple documents thru an instance of SingleDocumentCopier.
SingleDocumentCopier exposes an event CopyCompleted that is fired when a file is copied.
Suppose that, I am copying 10 files, instead of raising SingleDocumentCopier.CopyCompleted 10 times,
I would like to expose an event, MultipleDocumentCopier.MultipleCopyCompleted.
But is there a standard way/technique to combine multiple events and fire it once?
I would like to raise MultipleDocumentCopier.MultipleCopyCompleted only once
within 'MultipleDocumentCopier.singleDocumentCopier_CopyCompleted', instead of 10 times.
Here is the sample code
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace CombineEvents
{
public class Program
{
public static void Main(string[] args)
{
var copier = new MultipleDocumentCopier();
copier.MultipleCopyCompleted += MultipleDocumentCopyCompleted;
copier.CopyDocuments(new[] {"File1", "File2", "File3"});
}
private static void MultipleDocumentCopyCompleted(
object sender, FileNameEventArgs e)
{
Debug.Print("Following documents have been copied");
foreach (var fileName in e.FileNames)
{
Debug.Print("\t\t\"{0}\"", fileName);
}
}
}
internal class SingleDocumentCopier
{
public event EventHandler CopyCompleted;
protected virtual void OnCopyCompleted()
{
if (CopyCompleted != null) CopyCompleted(this, EventArgs.Empty);
}
public void Copy(string fileName)
{
Debug.Print("Copying = '{0}'", fileName);
OnCopyCompleted();
}
}
public class MultipleDocumentCopier
{
public event EventHandler<FileNameEventArgs> MultipleCopyCompleted;
protected virtual void OnCopyCompleted(FileNameEventArgs e)
{
EventHandler<FileNameEventArgs> completed = MultipleCopyCompleted;
if (completed != null) completed(this, e);
}
public void CopyDocuments(IEnumerable<string> fileNames)
{
var copier = new SingleDocumentCopier();
copier.CopyCompleted += singleDocumentCopier_CopyCompleted;
foreach (var fileName in fileNames)
{
copier.Copy(fileName);
}
}
public static void singleDocumentCopier_CopyCompleted(
object sender, EventArgs e)
{
// I want to raise "MultipleDocumentCopier.MultipleCopyCompleted" when
// all files, `fileNames` in "CopyDocuments" have been copied,
// not for every file being copied.
}
}
public class FileNameEventArgs : EventArgs
{
private readonly List<string> _FileNames;
public List<string> FileNames
{
get { return _FileNames; }
}
public FileNameEventArgs(IEnumerable<string> fileNames)
{
_FileNames = fileNames.ToList();
}
}
}
Why not call MultipleDocumentCopier.OnCopyCompleted from the end of CopyDocuments, and forget singleDocumentCopier_CopyCompleted entirely?
Or maybe this is pseudocode, and your real code is more complicated? Maybe you could keep a collection of outstanding file names inside MultipleDocumentCopier, and each time the singleDocumentCopier_CopyCompleted is raised, you remove one document from the collection. Once the collection becomes empty you call MultipleDocumentCopier.OnCopyCompleted.
Edit: Re 'is there a standard way?' -- not that I'm aware of in C#; F# has an interesting set of mechanisms for combining events like this, but I assume a change in programming language isn't an option.