I'm writing a custom analyzer rule using Roslyn.
I want to find a method which is a handler for some event (via subscription).
Like this:
public class Counter
{
public event EventHandler ThresholdReached;
}
public class TestEvent
{
public TestEvent()
{
Counter с = new Counter();
с.ThresholdReached += OnThresholdReached;
}
private void OnThresholdReached(object sender, EventArgs e)
{
}
}
In my realization it looks:
private static void HandleMethodDeclaration(SyntaxNodeAnalysisContext context)
{
MethodDeclarationSyntax methodDeclaration = (MethodDeclarationSyntax)context.Node;
if (methodDeclaration.Identifier.IsMissing)
{
return;
}
IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration);
}
I don't know how to detect that OnThresholdReached is subscription of Event ThresholdReached. If someone knows how to do it, please help=)
In an analyzer, you cannot know only from looking at a MethodDeclarationSyntax, whether that method is converted to a delegate or not. Because of that, you can not know (only from looking at a MethodDeclarationSyntax) whether that delegate is passed to the add accessor of an event or not.
First of all, remember that a Roslyn analyzer can only see usages in the current assembly (project). If your method is converted to a delegate in another assembly, there is no way for the analyzer to see this.
Secondly, remember that
с.ThresholdReached += OnThresholdReached;
may be expressed as
EventHandler handler = OnThresholdReached;
с.ThresholdReached += handler;
If you only want to detect the first case, you can look at AssignmentExpressionSyntax instances of kind SyntaxKind.AddAssignmentExpression, and analyze those.
If you want to detect all cases where a method group is converted to a delegate, you need to look at all instances of type SimpleNameSyntax and analyze those as follows:
void Analyze(SyntaxNodeAnalysisContext context)
{
var node = context.Node as SimpleNameSyntax;
// we're only interested in delegates
var type = context.SemanticModel.GetTypeInfo(node, context.CancellationToken).ConvertedType;
if (type == null || type.TypeKind != TypeKind.Delegate)
{
return;
}
// we're only interested in methods from the current assembly
var symbol = context.SemanticModel.GetSymbolInfo(node, context.CancellationToken).Symbol;
if (symbol == null ||
symbol.Kind != SymbolKind.Method ||
!symbol.ContainingAssembly.Equals(context.SemanticModel.Compilation.Assembly))
{
return;
}
// now you know symbol is a method in the same assembly, that is converted to a delegate
}
To find the source code for that method, see https://stackoverflow.com/a/45362532/1403794.
Related
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.
I'm struggling to understand why it's okay to attach a 'normal' method as a subscriber to a publisher event, and also a delegate.
For example...
public class Caller
{
public string Name { get; set; }
public event EventHandler<RuedaEventArgs> MakeRuedaCall;
public virtual void OnMakeRuedaCall(RuedaEventArgs args) {
if (MakeRuedaCall != null) {
MakeRuedaCall(this, args);
}
}
}
This is my publisher class where I define and raise the event. I'm also making use of some custom event arguments.
public class Salsera {
public Salsera(Caller caller) {
caller.MakeRuedaCall += MakeMovement;
}
public void MakeMovement(object source, RuedaEventArgs args) {
if (args.CallName == "Vacilala") {
Turn();
}
if (args.CallName == "Patin") {
MoveToOutside();
}
}
private void MoveToOutside() {
Console.WriteLine("Ladies move to the outside....");
}
private void Turn() {
Console.WriteLine("Ladies turn....");
}
}
This is a class where I add a method as a subscriber to the event in the constructor.
Suppose I then have somewhere else...
Caller matt = new Caller();
EventHandler<RuedaEventArgs> anonyMouseFunc = (sender, eventArgs) =>
{
switch (eventArgs.CallName) {
case "Patin":
Console.WriteLine("Adding a new subscriber for Patin");
break;
case "Vacilala":
Console.WriteLine("Adding a new subscriber for Vacilala");
break;
}
};
matt.MakeRuedaCall += anonyMouseFunc;
Sorry if this seems like a silly question but why is it that you can subscribe a 'normal' method (assuming it matches the delegate signiture) to an event, and also an anonymous method as a delegate to an event.
i.e. how does public event EventHandler<RuedaEventArgs> MakeRuedaCall; handle both options?
Many thanks,
Sorry if this seems like a silly question but why is it that you can subscribe a 'normal' method (assuming it matches the delegate signiture) to an event, and also an anonymous method as a delegate to an event.
You're subscribing a delegate in both cases. In this case:
caller.MakeRuedaCall += MakeMovement;
... you're using a method group conversion to convert MakeMovement (which is a method group in spec terminology) into a delegate instance. That code is (almost entirely) equivalent to:
caller.MakeRuedaCall += new EventHandler<RuedaEventArgs>(MakeMovement);
Or to think of it another way, it's equivalent to:
EventHandler<RuedaEventArgs> handler = MakeMovement;
caller.MakeRuedaCall += handler;
This ability to create delegates from regular methods isn't just for event handling, of course - you can use it for LINQ and anywhere else you use delegates, too.
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)
I would like to ensure that I only subscribe once in a particular class for an event on an instance.
For example I would like to be able to do the following:
if (*not already subscribed*)
{
member.Event += new MemeberClass.Delegate(handler);
}
How would I go about implementing such a guard?
I'm adding this in all the duplicate questions, just for the record. This pattern worked for me:
myClass.MyEvent -= MyHandler;
myClass.MyEvent += MyHandler;
Note that doing this every time you register your handler will ensure that your handler is registered only once.
If you are talking about an event on a class that you have access to the source for then you could place the guard in the event definition.
private bool _eventHasSubscribers = false;
private EventHandler<MyDelegateType> _myEvent;
public event EventHandler<MyDelegateType> MyEvent
{
add
{
if (_myEvent == null)
{
_myEvent += value;
}
}
remove
{
_myEvent -= value;
}
}
That would ensure that only one subscriber can subscribe to the event on this instance of the class that provides the event.
EDIT please see comments about why the above code is a bad idea and not thread safe.
If your problem is that a single instance of the client is subscribing more than once (and you need multiple subscribers) then the client code is going to need to handle that. So replace
not already subscribed
with a bool member of the client class that gets set when you subscribe for the event the first time.
Edit (after accepted): Based on the comment from #Glen T (the submitter of the question) the code for the accepted solution he went with is in the client class:
if (alreadySubscribedFlag)
{
member.Event += new MemeberClass.Delegate(handler);
}
Where alreadySubscribedFlag is a member variable in the client class that tracks first subscription to the specific event.
People looking at the first code snippet here, please take note of #Rune's comment - it is not a good idea to change the behavior of subscribing to an event in a non-obvious way.
EDIT 31/7/2009: Please see comments from #Sam Saffron. As I already stated and Sam agrees the first method presented here is not a sensible way to modify the behavior of the event subscription. The consumers of the class need to know about its internal implementation to understand its behavior. Not very nice.
#Sam Saffron also comments about thread safety. I'm assuming that he is referring to the possible race condition where two subscribers (close to) simultaneously attempt to subscribe and they may both end up subscribing. A lock could be used to improve this. If you are planning to change the way event subscription works then I advise that you read about how to make the subscription add/remove properties thread safe.
As others have shown, you can override the add/remove properties of the event. Alternatively, you may want to ditch the event and simply have the class take a delegate as an argument in its constructor (or some other method), and instead of firing the event, call the supplied delegate.
Events imply that anyone can subscribe to them, whereas a delegate is one method you can pass to the class. Will probably be less surprising to the user of your library then, if you only use events when you actually want the one-to-many semantics it usually offers.
You can use Postsharper to write one attribute just once and use it on normal Events. Reuse the code. Code sample is given below.
[Serializable]
public class PreventEventHookedTwiceAttribute: EventInterceptionAspect
{
private readonly object _lockObject = new object();
readonly List<Delegate> _delegates = new List<Delegate>();
public override void OnAddHandler(EventInterceptionArgs args)
{
lock(_lockObject)
{
if(!_delegates.Contains(args.Handler))
{
_delegates.Add(args.Handler);
args.ProceedAddHandler();
}
}
}
public override void OnRemoveHandler(EventInterceptionArgs args)
{
lock(_lockObject)
{
if(_delegates.Contains(args.Handler))
{
_delegates.Remove(args.Handler);
args.ProceedRemoveHandler();
}
}
}
}
Just use it like this.
[PreventEventHookedTwice]
public static event Action<string> GoodEvent;
For details, look at Implement Postsharp EventInterceptionAspect to prevent an event Handler hooked twice
You would either need to store a separate flag indicating whether or not you'd subscribed or, if you have control over MemberClass, provide implementations of the add and remove methods for the event:
class MemberClass
{
private EventHandler _event;
public event EventHandler Event
{
add
{
if( /* handler not already added */ )
{
_event+= value;
}
}
remove
{
_event-= value;
}
}
}
To decide whether or not the handler has been added you'll need to compare the Delegates returned from GetInvocationList() on both _event and value.
I know this is an old Question, but the current Answers didn't work for me.
Looking at C# pattern to prevent an event handler hooked twice (labelled as a duplicate of this question), gives Answers that are closer, but still didn't work, possibly because of multi-threading causing the new event object to be different or maybe because I was using a custom event class. I ended up with a similar solution to the accepted Answer to the above Question.
private EventHandler<bar> foo;
public event EventHandler<bar> Foo
{
add
{
if (foo == null ||
!foo.GetInvocationList().Select(il => il.Method).Contains(value.Method))
{
foo += value;
}
}
remove
{
if (foo != null)
{
EventHandler<bar> eventMethod = (EventHandler<bar>)foo .GetInvocationList().FirstOrDefault(il => il.Method == value.Method);
if (eventMethod != null)
{
foo -= eventMethod;
}
}
}
}
With this, you'll also have to fire your event with foo.Invoke(...) instead of Foo.Invoke(...). You'll also need to include System.Linq, if you aren't already using it.
This solution isn't exactly pretty, but it works.
I did this recently and I'll just drop it here so it stays:
private bool subscribed;
if(!subscribed)
{
myClass.MyEvent += MyHandler;
subscribed = true;
}
private void MyHandler()
{
// Do stuff
myClass.MyEvent -= MyHandler;
subscribed = false;
}
Invoke only distinct elements from GetInvocationList while raising:
using System.Linq;
....
public event HandlerType SomeEvent;
....
//Raising code
foreach (HandlerType d in (SomeEvent?.GetInvocationList().Distinct() ?? Enumerable.Empty<Delegate>()).ToArray())
d.Invoke(sender, arg);
Example unit test:
class CA
{
public CA()
{ }
public void Inc()
=> count++;
public int count;
}
[TestMethod]
public void TestDistinctDelegates()
{
var a = new CA();
Action d0 = () => a.Inc();
var d = d0;
d += () => a.Inc();
d += d0;
d.Invoke();
Assert.AreEqual(3, a.count);
var l = d.GetInvocationList();
Assert.AreEqual(3, l.Length);
var distinct = l.Distinct().ToArray();
Assert.AreEqual(2, distinct.Length);
foreach (Action di in distinct)
di.Invoke();
Assert.AreEqual(3 + distinct.Length, a.count);
}
[TestMethod]
public void TestDistinctDelegates2()
{
var a = new CA();
Action d = a.Inc;
d += a.Inc;
d.Invoke();
Assert.AreEqual(2, a.count);
var distinct = d.GetInvocationList().Distinct().ToArray();
Assert.AreEqual(1, distinct.Length);
foreach (Action di in distinct)
di.Invoke();
Assert.AreEqual(3, a.count);
}
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.