Unity EventHandler - c#

I am new to c# and unity, and want to use EventHandler to announce other script for doing something.
Some code have been subscribed to this event.
What "if(RefreshLevel != null)" is actually do, what is the content of "RefreshLevel", and why this event is not triggered?
using System;
using UnityEngine;
public class GameLevel : MonoBehaviour
{
public static GameLevel current;
private void Awake()
{
current = this;
}
private int level = 1;
private int manyItem;
private int burnedItem = 0;
public event EventHandler<LevelEventArgs> RefreshLevel;
// Start is called before the first frame update
void Start()
{
itemWorld = GameObject.Find("ItemWorld");
manyItem = itemWorld.transform.childCount;
}
public void LevelUp()
{
burnedItem += 1;
if (burnedItem < manyItem)
{
level += 1;
if(RefreshLevel != null)
{
RefreshLevel(this, new LevelEventArgs(level));
}
Debug.Log("Burned Item: " + burnedItem);
Debug.Log("Level: " + level);
}
else if(burnedItem == manyItem)
{
Debug.Log("Burned Item: " + burnedItem);
Debug.Log("Ghost Dead");
}
}
}
public class LevelEventArgs : EventArgs
{
public LevelEventArgs(int level)
{
Level = level;
}
public int Level;
}

TL;DR
if(RefreshLevel != null)
does nothing else than checking if there is anyone "listening" to that event. As long as nobody attached a listener/callback to the event (the invocation list is empty) it is equal to null and invoking it would throw a NullReferenceException.
You can also write it as
RefreshLevel?.Invoke(this, new LevelEventArgs(level));
which is a) shorter to write and b) makes clearer that this is an event and not a normal method.
Background
EventHandler<T>
public delegate void EventHandler(object? sender, EventArgs e);
is just a delegate, meaning a template for a method signature (similar to an interface for classes)
Then the script is using it as an event which has a special meaning
Events are a special kind of multicast delegate that can only be invoked from within the class or struct where they are declared (the publisher class). If other classes or structs subscribe to the event, their event handler methods will be called when the publisher class raises the event. For more information and code examples, see Events and Delegates.
And behind it there is a MulticastDelegate
Represents a multicast delegate; that is, a delegate that can have more than one element in its invocation list.
That "invocation list" are the registered callbacks.
And finally there is the operator MulticastDelegate.Inequality which returns
true if d1 and d2 do not have the same invocation lists; otherwise, false
And
Two delegates are equal if they are not null and are of exactly the same type, their invocation lists contain the same number of elements, and every element in the invocation list of the first delegate is equal to the corresponding element in the invocation list of the second delegate.
So if you compare an event to null it is true, as long as there are no elements in the invocation list.
Further notes
Some code have been subscribed to this event.
Allow me to claim that this is not true. If your event is never invoked (but your other conditions met) it means you nowhere have registered any callbacks to the event like e.g.
private void Start()
{
yourGameLevel.RefreshLevel += OnGameLevelRefreshed;
}
private void OnGameLevelRefreshed(object sender, LevelEventArgs args)
{
...
}
or it could simply mean that the GameLevel instance you registered the callbacks for is not the same as the one you are looking at.
If this is able to change (assuming that due to the GameLevel.current thing) you might want to rather make your event static since you anyway pass in the reference for the sender in case someone needs it.
public static event EventHandler<LevelEventArgs> RefreshLevel;
and then rather go
private void Start()
{
GameLevel.RefreshLevel += OnGameLevelRefreshed;
}
private void OnGameLevelRefreshed(object sender, LevelEventArgs args)
{
...
}
this way you can be sure that even if the current instance is changed/destroyed you are still receiving any of the invoked events.

if RefreshLevel != null, that means someone has subscribed to the event.
if RefreshLevel == null, that means no one has subscribed to the event.

Related

How to use events in c#?

I am completely new to events in c# and this is what I want to do:
I have two methods:
OpenPage1();
OpenPage2();
When either of these methods is called, I want a third method named as PerformSomeTask() to be called.
I believe that this can be done by event handling. Could anyone tell me how to do this?
All you have to do in your class is to add an event with a correct eventHandler (Action in your example). The eventHandler should correspond the method that will subscribe to this event.
Then you fire the event from the openPage Methods.
You must check for null in case no one subscribed to this event.
public class Foo
{
public event Action theEvent;
public void OpenPage1()
{
if (theEvent != null)
theEvent();
}
public void OpenPage2()
{
if (theEvent != null)
theEvent();
}
}
public class Bar
{
public int Counter { get; set; }
public void PerformSomeTask()
{
Counter++;
}
}
And here's a test that you can run to see it all together:
[TestMethod]
public void TestMethod1()
{
var foo = new Foo();
var bar = new Bar();
foo.theEvent += bar.PerformSomeTask;
foo.OpenPage1();
foo.OpenPage2();
Assert.AreEqual(2, bar.Counter);
}
Events is a big part of C#.
To be simple, you need first a delegate that describe type of called method. In your example, PerformSomeTask is void and take no parameters.
So declare in your class
public delegate void PerformSomeTask();
Then, you need to declare event, which is the member that will called to launch your function
public event PerformSomeTask OnPerformSomeTask;
On your both methods, OpenPage1 and OpenPage2, you need to check if someone subscribe to your event, if yes, call it.
if(OnPerformSomeTask != null)
OnPerformSomeTask();
This will launch every method that subscribe to your event. Subscribers can be multiple.
To subscribe, just do it like this :
YourClass.OnPerformSomeTask += MyVoidMethod;
[...]
public void MyVoidMethod() { DoSomething(); [...] }
Your void method will be called everytime your run OpenPage1 and OpenPage2
If you need some parameters, juste change your delegate to proceed.
public delegate void PerformSomeTask(string myParam);
Then, your methods will have this parameter as standard function parameter (call your event with your value to pass it as parameter to every subscriber function).

Assigning pointer to event for use later

This is abit difficult to word, so I am going to rely mostly on code.
BTW if you can word the question in a better light please dont hesitate giving your 2c!
class CustomEventArgs : EventArgs
{
public delegate void CustomEventHandler( Object sender, CustomEventArgs args );
public int data;
public CustomEventArgs (int _data)
{
data = _data;
}
}
This is the event that we will be using in this example.
class EventGenerator
{
public event CustomEventArgs.CustomEventHandler WeOccasion;
public EventGenerator ()
{
Task.Factory.StartNew( () =>
{
var index = 1;
// just loop and generate events every now and then
while (true)
{
Thread.Sleep( 1000 );
WeOccasion( this, new CustomEventArgs (++index));
}
});
}
}
This class just loops through firing off CustomEventHandler events.
class EventActivity
{
// EventActivity has an event of the same type as EventGenerator's
public event CustomEventArgs.CustomEventHandler WeOccasion;
// this is the part I cant seem to get right
public event CustomEventArgs.CustomEventHandler Source ( get; set; }
public bool Active {
set
{
if (value)
{
Source += DoWork;
}
else
{
Source -= DoWork;
}
}
}
private void DoWork( Object sender, CustomEventArgs frame);
}
Here is where I really need help. I want almost a pointer to an event in an another class of type CustomEventHandler that I can later assign event handlers to when I activate the activity.
Here is a usage example wrapped in a class;
class EventAssigner
{
EventGenerator Generator;
EventActivity DoSomeThing1;
EventActivity DoSomeThing2;
public EventAssigner ()
{
// init
Generator = new EventGenerator();
DoSomeThing1 = new EventActivity();
DoSomeThing2 = new EventActivity();
// assign sources
DoSomeThing1.Source = Generator.WeOccasion;
DoSomeThing2.Source = DoSomeThing1.WeOccasion;
// activate the first activity
DoSomeThing1.Active = true;
}
public void Activate2()
{
// activate the second activity
DoSomeThing2.Active = true;
}
public void Deactivate2()
{
// deactivate the second activity
DoSomeThing2.Active = false;
}
}
Obiously this code doesnt work, and I suppose thats what I am asking. Can you get this design pattern to work?
What you're asking to do isn't really possible with .NET events, and probably isn't as desirable as you might think. A bit of background should help explain why:
Properties have a basic pattern with get and set operations. These are invoked by accessing the property (for a get) and an assignment to the property (for a set):
var x = instance.Prop1; // access
instance.Prop1 = x; // assignment
When you access an event from outside the class (i.e. instance.Event) you are given the "public" face, which, like properties, has two operations: add handler and remove handler. These are invoked using the += and -= operators.
instance.Event += this.Handler; // add
instance.Event -= this.Handler; // remove
The important thing to notice that it doesn't have a "get" operation - there is no way to get a reference to the event outside the class; you can only modify the handlers registered.
When you access an event from within a class, you are given the "private" face, which is essentially a special collection of delegates (function pointers) to the registered event handlers. When you invoke the delegate, you're actually asking the framework to iterate through the registered event handlers and invoke those.
if(this.Event != null)
{
this.Event.Invoke(e, args); // raise event
}
This separation of public face and private face is what allows you have a nice simple event keyword which magically gives you an event. It is also what stops you passing a reference to the event around.
To pass the event into registration methods, you have to pass the object the event is attached to. If you have multiple classes which implement the same event and you want to register them all in the same way, you should have them implement an interface with the event (yes, events can be on interfaces) and write your method to accept the interface as an argument.
If I'm reading you correct, you want the line
DoSomeThing1.Source = Generator.WeOccasion;
to save the pointer to the WeOccasion event, so that you can add the DoWork call to it later, right?
I don't think that is possible with "normal" code, as the event is not a value, but rather like a property. Consider the following analogous code:
myProp = aPerson.Name; // attempt to save the name property for later
myProp = "Fred"; // intent is to set aPerson.Name = "Fred"
If you want this to work I'd suggest using reflection to find the event, and add to it using the EventInfo.AddEventHandler method (http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx)

Need help understanding Events in C#

I'm a beginner in C# and having hard times understanding Events in C# .. The book i read (Illustrated C# 2008) gives an example about it , and there are few thing i need to ask about , so i will past the code here and point out the things i don't understand .
public class MyTimerClass
{
public event EventHandler Elapsed;
private void OnOneSecond(object source, EventArgs args)
{
if (Elapsed != null)
Elapsed(source, args);
}
}
class ClassA
{
public void TimerHandlerA(object obj, EventArgs e) // Event handler
{
Console.WriteLine("Class A handler called");
}
}
class ClassB
{
public static void TimerHandlerB(object obj, EventArgs e) // Static
{
Console.WriteLine("Class B handler called");
}
}
class Program
{
static void Main( )
{
ClassA ca = new ClassA(); // Create the class object.
MyTimerClass mc = new MyTimerClass(); // Create the timer object.
mc.Elapsed += ca.TimerHandlerA; // Add handler A -- instance.
mc.Elapsed += ClassB.TimerHandlerB; // Add handler B -- static.
Thread.Sleep(2250);
}
}
Ok, now the line after declaring the event here public event EventHandler Elapsed;
which is private void OnOneSecond(object source, EventArgs args) i know that the two line after it is to check if the event contains methods or not , but what is OnOneSecound for ? or when it's called ? or what it's named .. it's not event handler i guess right ? and what's the relationship between Elapsed and OnOneSecond ?
sorry for the newbie question .. and thanks in advance :)
the OnOneSecond method will be called internally by the MyTimerClass when it needs to invoke the event.
This is a common pattern used by most controls, including the microsoft ones.
Basically you dont need to be checking if the event is set in multiple places, you just do it in this one method then call this method internally to raise the event.
I tend not to pass the event args to the OnXXX method though, for example.
public event EventHandler<EventArgs> SomeEvent;
protected virtual void OnSomeEvent()
{
if (this.SomeEvent !=null)
{
this.SomeEvent.Invoke(this,EventArgs.Empty);
}
}
then to raise it
this.OnSomeEvent();
This is the method, that you call to raise the event safely.
the problem is, you can basically call
Elapsed(source, args)
but if there is noone connected to the event, this will raise a Reference Null exception. as the event is null, when nobody hears on it.
a better solution is, that you directly add a subscriber to the events. then you can safely call it directly. as there will be allways a subscriber.
public event Action<EventArgs> Elapsed = val => { };
(note that with the = its directly assigned. val => { } is a Lambda expression, that defines a empty subscriber.)
Also, look into the Reactive Framework for .net
if you want to do a lot of event stuff, this is the correct solution for it.
That allows you to manually fire the events from thein the class.
That is the standard pattern for raising internal events that's why it is private.
OnOneSecond is just a helper method defined to raise the event. You can use events without such methods, it is just an established pattern to wrap the if (Elapsed != null) check in a method with a name that starts with On...
Technically you could just use Elapsed(source, args) instead of OnOneSecond(source, args), but this will throw NullReferenceException if there are no listeners registered.

In C#, why can't I test if a event handler is null anywhere outside of the class that it's defined?

I am sure that I am just not understanding something fundamental about events and/or delegates in C#, but why can't I do the Boolean tests in this code sample:
public class UseSomeEventBase {
public delegate void SomeEventHandler(object sender, EventArgs e);
public event SomeEventHandler SomeEvent;
protected void OnSomeEvent(EventArgs e) {
// CANONICAL WAY TO TEST EVENT. OF COURSE, THIS WORKS.
if (SomeEvent != null) SomeEvent(this, e);
}
}
public class UseSomeEvent : UseSomeEventBase {
public bool IsSomeEventHandlerNull() {
// "LEFT HAND SIDE" COMPILER ERROR
return SomeEvent == null;
}
}
class Program {
static void Main(string[] args) {
var useSomeEvent = new UseSomeEvent();
useSomeEvent.SomeEvent +=new UseSomeEventBase.SomeEventHandler(FuncToHandle);
// "LEFT HAND SIDE" COMPILER ERROR
if (useSomeEvent.SomeEvent == null) {
}
var useSomeEventBase = new UseSomeEventBase();
useSomeEventBase.SomeEvent += new UseSomeEventBase.SomeEventHandler(FuncToHandle);
// "LEFT HAND SIDE" COMPILER ERROR
if (useSomeEventBase.SomeEvent == null) {
}
}
static void FuncToHandle(object sender, EventArgs e) { }
}
An event is really just an "add" operation and a "remove" operation. You can't get the value, you can't set the value, you can't call it - you can just subscribe a handler for the event (add) or unsubscribe one (remove). This is fine - it's encapsulation, plain and simple. It's up to the publisher to implement add/remove appropriately, but unless the publisher chooses to make the details available, subscribers can't modify or access the implementation-specific parts.
Field-like events in C# (where you don't specify the add/remove bits) hide this - they create a variable of a delegate type and an event. The event's add/remove implementations just use the variable to keep track of the subscribers.
Inside the class you refer to the variable (so you can get the currently subscribed delegates, execute them etc) and outside the class you refer to the event itself (so only have add/remove abilities).
The alternative to field-like events is where you explicitly implement the add/remove yourself, e.g.
private EventHandler clickHandler; // Normal private field
public event EventHandler Click
{
add
{
Console.WriteLine("New subscriber");
clickHandler += value;
}
remove
{
Console.WriteLine("Lost a subscriber");
clickHandler -= value;
}
}
See my article on events for more information.
Of course the event publisher can also make more information available - you could write a property like ClickHandlers to return the current multi-cast delegate, or HasClickHandlersto return whether there are any or not. That's not part of the core event model though.
You can easily use a very simple approach here to not repeatedly subscribe to an event.
Either of the 2 approaches below can be used:
Flag approach : _getWarehouseForVendorCompletedSubscribed is a private variable initialized to false.
if (!_getWarehouseForVendorCompletedSubscribed)
{
_serviceClient.GetWarehouseForVendorCompleted += new EventHandler<GetWarehouseForVendorCompletedEventArgs>(_serviceClient_GetWarehouseForVendorCompleted);
_getWarehouseForVendorCompletedSubscribed = true;
}
Unsubscribe Approach :Include an unsubscribe everytime you want to subscribe.
_serviceClient.GetWarehouseForVendorCompleted -= new
EventHandler<GetWarehouseForVendorCompletedEventArgs>
(_serviceClient_GetWarehouseForVendorCompleted);
_serviceClient.GetWarehouseForVendorCompleted += new
EventHandler<GetWarehouseForVendorCompletedEventArgs>
(_serviceClient_GetWarehouseForVendorCompleted);
Here the answer:
using System;
delegate void MyEventHandler();
class MyEvent
{
string s;
public event MyEventHandler SomeEvent;
// This is called to raise the event.
public void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent();
}
}
public string IsNull
{
get
{
if (SomeEvent != null)
return s = "The EventHandlerList is not NULL";
else return s = "The EventHandlerList is NULL"; ;
}
}
}
class EventDemo
{
// An event handler.
static void Handler()
{
Console.WriteLine("Event occurred");
}
static void Main()
{
MyEvent evt = new MyEvent();
// Add Handler() to the event list.
evt.SomeEvent += Handler;
// Raise the event.
//evt.OnSomeEvent();
evt.SomeEvent -= Handler;
Console.WriteLine(evt.IsNull);
Console.ReadKey();
}
}
Here's a slightly different question
What value is there in testing an externally defined event for null?
As an external consumer of an event you can only do 2 operations
Add a handler
Remove a handler
The null or non-nullness of the event has no bearing on these 2 actions. Why do you want to run a test which provides no perceivable value?
It's a rule in place when using the 'event' keyword. When you create an event, you are restricting outside class interaction with the delegate to a "subscribe / unsubscribe" relationship, this includes cases of inheritance. Remember an event is essentially a property, but for method calls, it isn't really an object itself, so really it looks more like this:
public event SomeEventHandler SomeEvent
{
add
{
//Add method call to delegate
}
remove
{
//Remove method call to delegate
}
}
You'd have to do that from the base class. That's the exact reason that you did this:
protected void OnSomeEvent(EventArgs e) {
// CANONICAL WAY TO TEST EVENT. OF COURSE, THIS WORKS.
if (SomeEvent != null) SomeEvent(this, e);
}
You can't access events from a derived class. Also, you should make that method virtual, so that it can be overridden in a derived class.
Publisher of the event implicitly overload only += and -= operations, and other operations are not implemented in the publisher because of the obvious reasons as explained above, such as don't want to give control to subscriber to change events.
If we want to validate if a particular event is subscribed in the subscriber class, better publisher will set a flag in its class when event is subscriber and clear the flag when it is unsubscriber.
If subscriber can access the flag of publisher, very easily identifiable whether the particular event is subscriber or not by checking the flag value.

How do C# Events work behind the scenes?

I'm using C#, .NET 3.5. I understand how to utilize events, how to declare them in my class, how to hook them from somewhere else, etc. A contrived example:
public class MyList
{
private List<string> m_Strings = new List<string>();
public EventHandler<EventArgs> ElementAddedEvent;
public void Add(string value)
{
m_Strings.Add(value);
if (ElementAddedEvent != null)
ElementAddedEvent(value, EventArgs.Empty);
}
}
[TestClass]
public class TestMyList
{
private bool m_Fired = false;
[TestMethod]
public void TestEvents()
{
MyList tmp = new MyList();
tmp.ElementAddedEvent += new EventHandler<EventArgs>(Fired);
tmp.Add("test");
Assert.IsTrue(m_Fired);
}
private void Fired(object sender, EventArgs args)
{
m_Fired = true;
}
}
However, what I do not understand, is when one declares an event handler
public EventHandler<EventArgs> ElementAddedEvent;
It's never initialized - so what, exactly, is ElementAddedEvent? What does it point to? The following won't work, because the EventHandler is never initialized:
[TestClass]
public class TestMyList
{
private bool m_Fired = false;
[TestMethod]
public void TestEvents()
{
EventHandler<EventArgs> somethingHappend;
somethingHappend += new EventHandler<EventArgs>(Fired);
somethingHappend(this, EventArgs.Empty);
Assert.IsTrue(m_Fired);
}
private void Fired(object sender, EventArgs args)
{
m_Fired = true;
}
}
I notice that there is an EventHandler.CreateDelegate(...), but all the method signatures suggest this is only used for attaching Delegates to an already existing EventHandler through the typical ElementAddedEvent += new EventHandler(MyMethod).
I'm not sure if what I am trying to do will help... but ultimately I'd like to come up with an abstract parent DataContext in LINQ whose children can register which table Types they want "observed" so I can have events such as BeforeUpdate and AfterUpdate, but specific to types. Something like this:
public class BaseDataContext : DataContext
{
private static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> m_ObservedTypes = new Dictionary<Type, Dictionary<ChangeAction, EventHandler>>();
public static void Observe(Type type)
{
if (m_ObservedTypes.ContainsKey(type) == false)
{
m_ObservedTypes.Add(type, new Dictionary<ChangeAction, EventHandler>());
EventHandler eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
m_ObservedTypes[type].Add(ChangeAction.Insert, eventHandler);
eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
m_ObservedTypes[type].Add(ChangeAction.Update, eventHandler);
eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
m_ObservedTypes[type].Add(ChangeAction.Delete, eventHandler);
}
}
public static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> Events
{
get { return m_ObservedTypes; }
}
}
public class MyClass
{
public MyClass()
{
BaseDataContext.Events[typeof(User)][ChangeAction.Update] += new EventHandler(OnUserUpdate);
}
public void OnUserUpdated(object sender, EventArgs args)
{
// do something
}
}
Thinking about this made me realize I don't really understand what's happening under the hod with events - and I would like to understand :)
I've written this up in a fair amount of detail in an article, but here's the summary, assuming you're reasonably happy with delegates themselves:
An event is just an "add" method and a "remove" method, in the same way that a property is really just a "get" method and a "set" method. (In fact, the CLI allows a "raise/fire" method as well, but C# never generates this.) Metadata describes the event with references to the methods.
When you declare a field-like event (like your ElementAddedEvent) the compiler generates the methods and a private field (of the same type as the delegate). Within the class, when you refer to ElementAddedEvent you're referring to the field. Outside the class, you're referring to the field.
When anyone subscribes to an event (with the += operator) that calls the add method. When they unsubscribe (with the -= operator) that calls the remove.
For field-like events, there's some synchronization but otherwise the add/remove just call Delegate.Combine/Remove to change the value of the auto-generated field. Both of these operations assign to the backing field - remember that delegates are immutable. In other words, the autogenerated code is very much like this:
// Backing field
// The underscores just make it simpler to see what's going on here.
// In the rest of your source code for this class, if you refer to
// ElementAddedEvent, you're really referring to this field.
private EventHandler<EventArgs> __ElementAddedEvent;
// Actual event
public EventHandler<EventArgs> ElementAddedEvent
{
add
{
lock(this)
{
// Equivalent to __ElementAddedEvent += value;
__ElementAddedEvent = Delegate.Combine(__ElementAddedEvent, value);
}
}
remove
{
lock(this)
{
// Equivalent to __ElementAddedEvent -= value;
__ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value);
}
}
}
The initial value of the generated field in your case is null - and it will always become null again if all subscribers are removed, as that is the behaviour of Delegate.Remove.
If you want a "no-op" handler to subscribe to your event, so as to avoid the nullity check, you can do:
public EventHandler<EventArgs> ElementAddedEvent = delegate {};
The delegate {} is just an anonymous method which doesn't care about its parameters and does nothing.
If there's anything that's still unclear, please ask and I'll try to help!
Under the hood, events are just delegates with special calling conventions. (For example, you don't have to check for nullity before raising an event.)
In pseudocode, Event.Invoke() breaks down like this:
If Event Has Listeners
Call each listener synchronously on this thread in arbitrary order.
Since events are multicast, they will have zero or more listeners, held in a collection. The CLR will loop through them, calling each in an arbitrary order.
One big caveat to remember is that event handlers execute in the same thread as the event is raised in. It's a common mental error to think of them as spawning a new thread. They do not.

Categories

Resources