C# Creating an Event System with delegates - c#

I am making a game using Unity and creating a simple event system for it. The system allows subscribing to events with a callback delegate.
The code for the system is here:
public static class Events {
#region fields
public delegate void EventCallback();
public static Dictionary<Event, EventCallback> events;
public enum Event {
PlayerDied,
EnterGoal,
ExitGoal
}
#endregion
static Events() {
Reset();
}
public static void Reset() {
events = new Dictionary<Event, EventCallback>();
}
public static void SubscribeToEvent(EventCallback callback, Event type) {
if (!events.ContainsKey(type)) {
events.Add(type, callback);
} else {
events[type] += callback;
}
}
public static void OverrideSubscription(EventCallback callback, Event type) {
if (!events.ContainsKey(type)) {
events.Add(type, callback);
} else {
events[type] = callback;
}
}
public static void UnsubscribeFromEvent(EventCallback callback, Event type) {
if (events.ContainsKey(type)) {
events[type] -= callback;
}
}
public static void FireEvent(Event type) {
if (!events.ContainsKey(type)) {
return;
}
events[type]?.Invoke();
}
}
My problem is this:
Quite often in the game the object which subscribes to some event will be destroyed (and therefore nullified) but the callbacks are still called.
To demonstrate this I created tests for it. The 2 tests below fail.
[Test]
public void CallbackIsNotCalledAfterReassigning() {
Events.SubscribeToEvent(callbacks.A, Events.Event.PlayerDied);
// Reassign
callbacks = new CallbackContainer();
Events.FireEvent(Events.Event.PlayerDied);
Assert.AreEqual(0, callbacks.a, "Test function was called after reassigning");
Assert.AreEqual(0, a, "Old instance method is still being called");
}
[Test]
public void CallbackIsNotCalledAfterNullifying() {
Events.SubscribeToEvent(callbacks.A, Events.Event.PlayerDied);
// Nullify
callbacks = null;
Events.FireEvent(Events.Event.PlayerDied);
Assert.AreEqual(0, a, "Test function was not called");
}
And the CallbackContainer class is here:
public class CallbackContainer {
public int a, b, c;
public CallbackContainer() {
int a = 0;
int b = 0;
int c = 0;
}
public Action TestFunction;
public void A() {
EventsTest.a++;
a++;
}
public void B() {
EventsTest.b++;
b++;
}
public void C() {
EventsTest.c++;
c++;
}
}
Is there a workaround for this problem? Is there a better and more correct way to creating an event system?
Any help or advice is appreciated.

I had forgotten about Unitys own delegate OnDestroy which is called before an object is destyroyed or before another scene is loaded.

Related

c# dynamic event subscription and unsubscription

I want to be able to have an object add one of its methods to an EventHandler that is passed to it and give said method the ability to remove itself from the EventHandler.
public class EventRaiser {
public event EventHandler event1
public event EventHandler event2
public void fire() {
event1?.Invoke(this, null);
event2?.Invoke(this, null);
}
}
public class EventSubscriber {
EventHandler eh;
public EventSubscriber(EventHandler eh) {
this.eh = eh;
eh += receive;
}
public void receive(object obj, EventArgs data) {
// Do stuff.
if(condition) eh -= receive;
}
}
public class MainClass {
public void Main() {
EventRaiser er = new EventRaiser();
EventSubscriber es1 = new EventSubscriber(er.event1);
EventSubscriber es2 = new EventSubscriber(er.event2);
er.fire();
}
}
The above code does not compile as I cannot even pass er.event1 or er.event2 to EventSubscriber ("The event can only appear in the left hand side of +=..."). Removing the event keyword from the EventHandlers fixes this issue but unsubscribing does not work properly. Is there a way to make this work? Use pointers maybe?
The problem here comes from you passing an EventHandler, not the list holding the delegates behind it itsself. Basically the "list of method pointers" to your handlers.
As you can see, in the declaration of event1 you have the keyword event, which is missing when you pass it somewhere else.
Unfortunately you cannot extract the "delegate holder" of an event easily.
Basically at the time you want to register your handler to an event you somehow need a compile time reference to it, in order to be able to += and -= to it.
You could do the following:
public class EventRaiser
{
public delegate void Event1(string args);
public List<Event1> handlers = new List<Event1>();
public void register(Event1 handler)
{
handlers.Add(handler);
}
public void unregister(Event1 handler)
{
handlers.Remove(handler);
}
public void fire()
{
handlers.ForEach(handler => handler("myEventArgs"));
}
}
public class EventSubscriber
{
Action<Event1> registerAction;
Action<Event1> unregisterAction;
public EventSubscriber(Action<Event1> register, Action<Event1> unregister)
{
registerAction = register;
unregisterAction = unregister;
registerAction(receive);
}
public void receive(string args)
{
// Do stuff.
unregisterAction(receive);
}
}
public class MainClass
{
public void Main()
{
EventRaiser er = new EventRaiser();
EventSubscriber es1 = new EventSubscriber(er.register, er.unregister);
er.fire();
}
}

How to pass abstract method as reference?

I will admit, i am doing homework and i am stuck on this one question (Part A). How do i pass the notice method as reference to the railway signal ? Can't i just find out which class was called in the abstract constructor and then print the class name within the notify method? For example:
RailwayUser
private string className;
public RailwayUser()
{
Type type = this.GetType();
className = type.Name;
}
public void PrintClassName()
{
Console.Writeline(className);
}
RailwaySignal Class
public void Notify()
{
foreach(RailwayUser u in _watches)
{
u.PrintClassName();
u.Notice(State)
}
}
This kind of code / design is flawed, since what it does is RailwayUser, registers the object reference with the _watchers List in the RailWaySignal class, which in turn calls the public Notice method on each user when Notify is invoked, which is not how Event Signaling or Function Pointer works. In fact public _watchers is dangerous, as it can be cleared by any user, though that can be moderated using property access
Code with Issue
public void Notify()
{
foreach(RailwayUser u in _watches)
{
u.PrintClassName();
u.Notice(State)
}
}
Following shall be the actual code using events and delegates:
Correct Version
Code Snippet Online - https://www.jdoodle.com/embed/v0/uEc
void Main()
{
List<RailwayUser> railwayUsers = new List<RailwayUser>();
railwayUsers.Add(new RailwayUser());
railwayUsers.Add(new RailwayUser());
RailwayUser.TestNotification();
}
public enum Colour
{
Red,
Green,
NoSignal
}
public class RailwaySignal
{
public string Name {get; set;}
public RailwaySignal(string railwaySignalName)
{
Name = railwaySignalName;
}
// Delegate for handling event
public delegate void RailwaySignalEventHandler(object source, Colour e);
// Delagate object for handling event
private RailwaySignalEventHandler _railwaySignalEvent;
// Event Accessor
public event RailwaySignalEventHandler RailwaySignalEvent
{
add
{
lock (this)
{
_railwaySignalEvent += value;
}
}
remove
{
lock (this)
{
_railwaySignalEvent -= value;
}
}
}
// Invoke Event for subscribed clients
private void Notify()
{
if (_railwaySignalEvent != null)
_railwaySignalEvent.Invoke(this, Colour.Green);
}
// Test the Event Invocation
public void TestEvent()
{
Notify();
}
}
public class RailwayUser
{
private static RailwaySignal railwaySignal { get; set;} = new RailwaySignal("Signal1");
public RailwayUser()
{
railwaySignal.RailwaySignalEvent += this.Notice;
}
public static void TestNotification()
{
railwaySignal.TestEvent();
}
public void Notice(object sender, Colour color)
{
Console.WriteLine($"Notice Called, Colour is :: {color}, Sender is :: {((RailwaySignal)sender).Name}");
}
}
Result
Notice Called, Colour is :: Green, Sender is :: Signal1
Notice Called, Colour is :: Green, Sender is :: Signal1
Important Details
Signature of the event is, (object source, Colour e) which helps in passing the relevant information across to the RailwayUser called, We now know the RailwaySignal triggering the notification to the RailwayUser and its Colour value
Event / Delegate has same signature as called method (which is the basis of working of Delegate / function pointers)
For simplification RailwayUser is a non abstract class
Event is executed using Notify() method inside the RailwaySignal, we are calling it artificially using TestNotification() inside RailwayUser just for demo purpose, but ideally it shall be internally triggered and shall pass on current state like Colour
Pre-defined delegates like Func, Action are quite often used for similar notification mechanism, They internally works using similar mechanism, though declaring an explicit event which is internally a delegate is a well defined pattern, especially for the Ui controls
Standard events exposed by the .Net framework have the signature object sender, EventArgs e, where EventArgs can wrap all information from Event executor (RailwaySignal) to Event receiver (RailwayUser)
It seem like a Observer pattern.You can pass SubClass which inherit from RailwayUser object instance into RailwaySignal class
Your RailwayUser class need create public abstract void Notice(Colour state) method.
public abstract class RailwayUser
{
private string className;
public RailwayUser()
{
Type type = this.GetType();
className = type.Name;
}
public void PrintClassName()
{
Console.WriteLine(className);
}
public abstract void Notice(Colour state);
}
Driver class can inherit RailwayUser class then override Notice method.
public class Driver : RailwayUser
{
public override void Notice(Colour state)
{
Console.WriteLine($"Driver see the {state.ToString()}");
}
}
There are
List<RailwayUser> _watches contain observable object
use SubScript(RailwayUser user) subscription user on _watches List.
RailwayUser Notify() to invoke all your observable Notify method.
look like this.
public class RailwaySignal
{
private List<RailwayUser> _watches;
public Colour Stata { get; set; }
public RailwaySignal()
{
_watches = new List<RailwayUser>();
}
public void SubScript(RailwayUser user)
{
_watches.Add(user);
}
public void Notify()
{
foreach (RailwayUser u in _watches)
{
u.PrintClassName();
u.Notice(Stata);
}
}
}
sample:https://dotnetfiddle.net/GcdGMy
You can also use event to pass method into RailwaySignal then invoke Notify method.
public enum Colour
{
Green,
Red,
Disable
}
public abstract class RailwayUser
{
private string className;
public RailwayUser()
{
Type type = this.GetType();
className = type.Name;
}
public void PrintClassName()
{
Console.WriteLine(className);
}
public abstract void Notice(Colour state);
}
public class Driver : RailwayUser
{
public override void Notice(Colour state)
{
Console.WriteLine("Driver see the "+ state.ToString());
}
}
public class Controller : RailwayUser
{
public override void Notice(Colour state)
{
Console.WriteLine("Controller see the " + state.ToString());
}
}
public class RailwaySignal
{
public delegate void NoticeEvent(Colour state);
public event NoticeEvent Notifys;
public Colour Stata { get; set; }
public void Notify()
{
if (Notifys != null)
{
Notifys(Stata);
}
}
}
use like this.
RailwaySignal railway = new RailwaySignal() { Stata = Colour.Green};
railway.Notifys += new Driver().Notice;
railway.Notifys += new Controller().Notice;
railway.Notify();
sample : https://dotnetfiddle.net/GcdGMy

Trigger event called by an another class

I want to triggered my event when my UI call my class, life this :
In my UI i call my class ScanClass with a string parameter
ScanMessage scn = new ScanMessage(message);
In my class ScanMessage i made this :
public class ScanMessage
{
public delegate void OnScanMessageReceived(string message);
public ScanMessage()
{
}
public ScanMessage(string message)
{
MessageReceived(message);
}
public event OnScanMessageReceived MessageReceived;
}
And i made this in my FirstViewModel
public FirstViewModel()
{
var scanMessage = new ScanMessage();
scanMessage.MessageReceived += ScanMessage_MessageReceived;
}
private void ScanMessage_MessageReceived(string message)
{
//Do something
}
Well, if i post here, it's because i have a problem and i can't fix it without help !
Thank.
The reason is the instance of ScanMessage is different. UI class has different instance and VM has different instance. Hence raising event on one instance is not being heard in other one.
If all your ScanMessage needs to do is to broadcast event, you can have its singleton instance
public class ScanMessage
{
public delegate void OnScanMessageReceived(string message);
private static ScanMessage _scanMessage = new ScanMessage();
private ScanMessage()
{
}
public static ScanMessage Instance
{
get
{
return _scanMessage;
}
}
public void BroadCastMessage(string message)
{
MessageReceived(message);
}
public event OnScanMessageReceived MessageReceived;
}
And then from UI you can call like
ScanMessage.Instance.BroadCastMessage(message);
and in your VM you can
public FirstViewModel()
{
ScanMessage.Instance.MessageReceived += ScanMessage_MessageReceived;
}
private void ScanMessage_MessageReceived(string message)
{
//Do something
}

RegisterCallback<T>(Action<T> func) , how do I store this function pointer in a class?

I'm trying to expose an API such that, I do the following
RegisterCallback<T>(Action<T> func)
{
someObj.FuncPointer = func;
}
Later on, I call func(obj) .. and the obj is of type T that the user said.
More concrete example:
var callbackRegistrar = new CBRegistrar();
callbackRegistrar.RegisterCallback<ISomeClass>(SomeFunc);
public static void SomeFunc(ISomeClass data)
{
//
}
EDIT: So I may not have been clear, so I'll add more code:
I want to make only "one" object of CBRegistrar, and connect it with many Callbacks, as such:
var callbackRegistrar = new CBRegistrar();
callbackRegistrar.RegisterCallback<ISomeClass>(SomeFunc);
callbackRegistrar.RegisterCallback<ISomeOtherClass>(SomeFunc2);
...
In fact the above code is called by reflecting over a directory of plugins.
The user puts this in their code -->
public static void SomeFunc(ISomeClass data)
{
//
}
public static void SumFunc2(ISomeOtherClass data)
{
//
}
It looks to me as if this is not possible using Generics, etc. What it looks like I might have to do is make an interface called IPlugin or something, and ask the user to do this ..
[PluginIdentifier(typeof(ISomeClass))]
public static void SomeFunc(IPluginData data)
{
var castedStuff = data as ISomeClass; // ISomeClass inherits from IPluginData
}
Seems like asking the user to do stuff that we should take care of, but anyway ...
You need a Action<T> func to store it in. There is a semantic check to make here: if someone calls RegisterCallback twice (with different values), do you want to replace the callback, or keep both ? Assuming the latter, someObj probably wants an event (indeed, this entire API could be exposed as an event), so - in the someObj class:
public event Action<T> FuncPointer;
private void InvokeCallback(T data) {
var handler = FuncPointer;
if(handler != null) handler(data);
}
Noting that RegisterCallback could be replaced entirely, still keeping the data on obj:
public event Action<T> Completed {
add { obj.FuncPointer += value; }
remove { obj.FuncPointer -= value; }
}
Then usage would be:
var callbackRegistrar = new CBRegistrar();
callbackRegistrar.Completed += SomeFunc;
Callback functions are not much used in C#. They've been replaced by events which are more elegant and easier to work with.
class CBRegistrar
{
public delegate void ActionRequiredEventHandler(object sender, ISomeClass e);
public event ActionRequiredEventHandler ActionRequired;
void RaiseActionRequiredEvent(ISomeClass parm)
{
if ( ActionRequired != null)
{
ActionRequired(this, parm);
}
}
}
class APIConsumer
{
var callbackRegistrar = new CBRegistrar();
public APIConsumer()
{
callbackRegistrar.ActionRequired += SomeFunc;
}
public void SomeFunc(object sender, ISomeClass data)
{
}
}
If you still want to use Callbacks, you can use Delegates which are more or less function pointer.
The CBRegistrar will need to be generic (if it's OK to keep a single callback type) or it can do some internal casting (if several callback types need to be registered).
public class CBRegistrar<T>
{
private Action<T> callback;
private Dictionary<Type, object> callbackMap;
public CBRegistrar()
{
this.callbackMap = new Dictionary<Type, object>();
}
public void RegisterCallback(Action<T> func)
{
this.callback = func;
}
public void RegisterGenericCallback<U>(Action<U> func)
{
this.callbackMap[typeof(U)] = func;
}
public Action<U> GetCallback<U>()
{
return this.callbackMap[typeof(U)] as Action<U>;
}
}
public interface ISomeClass
{
string GetName();
}
public class SomeClass : ISomeClass
{
public string GetName()
{
return this.GetType().Name;
}
}
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
var callbackRegistrar = new CBRegistrar<ISomeClass>();
callbackRegistrar.RegisterCallback(SomeFunc);
callbackRegistrar.RegisterGenericCallback<ISomeClass>(SomeFunc);
var someone = new SomeClass();
callbackRegistrar.GetCallback<ISomeClass>()(someone);
}
public static void SomeFunc(ISomeClass data)
{
// Do something
Console.WriteLine(data.GetName());
}
}
}

Subscriber method does not need to create an instance of delegate?

Having an event like this:
class ABC
{
delegate bool X (int a);
event X eventX;
}
ABC.eventX+=someMethod; //works
I assume the delegate is then created implicitly by compiler?
Yes, prior to .NET 2 you had to manually specify it:
ABC.eventX+=new X(someMethod);
But it is now created implicitly with this syntax:
ABC.eventX+=someMethod;
Yes, it's automatically created.
For example:
namespace ConsoleApplication5
{
class Program
{
static void Main(string[] args)
{
(new Program()).Entrance();
}
public void Entrance()
{
ABC a = new ABC();
a.eventX += callback;
}
protected bool callback(int a)
{
return true;
}
}
class ABC
{
public delegate bool X(int a);
public event X eventX;
}
}
The Program class will be this if you see in reflector:
internal class Program
{
// Methods
protected bool callback(int a)
{
return true;
}
public void Entrance()
{
ABC a = new ABC();
a.eventX += new ABC.X(this.callback);
}
private static void Main(string[] args)
{
new Program().Entrance();
}
}

Categories

Resources