How can I bind a handler to a static event without invoking the static constructor?


How can I bind a handler to a static event without invoking the static constructor?



I fear I know the answer to this but...



Is it possible to successfully bind to a static event that will raise during a static constructor? Or is this logically impossible?



My event handler is not being hit. I suspect it's because when I do this...


MyClass.MyEvent += MyEventHandler;



...the call to MyClass is running the static contructor, so the event inside that constructor has already been raised by the time the handler is bound, later in that line of code.


MyClass



Is that correct? Is there another way to do this? Is it at all possible to bind to a static event without causing the static constructor to execute?





..the event inside that constructor..? could you show your class definition or better, convert this into a minimal, complete example we could run?
– dlatikay
2 days ago





If your event is in the constructor then you need to have another handler object. You listen to that event and during the constructor of the first object have it alert the one you're listening to and that one raises the event.
– Michael Puckett II
2 days ago




3 Answers
3



The first time I read this question I immediately thought "No way...". But then I was like "hold on a second.. it may be possible.. hmmm". Ok, let's go to the code! Consider the following:


abstract class FooBaseNotifier
{
public static event Action FooTypeLoaded;

protected static void Notify() => FooTypeLoaded?.Invoke();
}

class Foo:FooBaseNotifier
{
static Foo() => Notify();
}



The key here is to play with C# static constructors rules. We use an abstract base class to define the event and a protected method to invoke it. It must be protected so that Foo can fire the event from the static constructor.



Now testing:


FooBaseNotifier.FooTypeLoaded += () => Console.WriteLine("Foo");
var foo = new Foo();

Console.ReadLine();



It works! This will print "Foo" to the console. Furthermore, due to the fact that the static event is a static member of the abstract base class you can do even this:


Foo.FooTypeLoaded += () => Console.WriteLine("Foo");
var foo = new Foo();

Console.ReadLine();



And Foo's static constructor is not called but until the line with new Foo() is reached!


new Foo()



Short answer: no.



Longer answer: Put the handler on some other class. FooHandlers.MyEvent. Then just have the Foo static constructor trigger those events. You’ll need to create some FooHandlers.InvokeMyEvent methods, since Foo won’t be able to do it directly.



As to the “why” of this, static constructors are run before and field of the type is accessed. An event has a backing field to hold the multicast delegate. That means accessing the event triggers the static constructor unavoidably before it can be assigned (that’s sort of the point of a static constructor, after all).



I generally try to avoid static constructors, because they tend to cause weird problems like this one... and if they ever throw any sort of exception, the behavior is strange.



When you declare an event using the event keyword, you are really declaring two methods, add and remove. Think of it like when you declare a property: under the covers, you are really declaring a set and a get method. Events are no different; you can actually override add and remove in code, with a custom event accesssor.


event


add


remove


set


get


add


remove



So when you call


MyClass.MyEvent += MyEventHandler;



you are really calling


MyClass.MyEvent.add(MyEventHandler); //not real code



Naturally, the static constructor has to be run whenever any method is accessed, to ensure the static state of the class is correct. It's a feature. So to defer execution of the static constructor while adding to the event is not possible, I'm afraid.



If you want some initialization to run later, extract it to a separate method and call it separately, or load it lazily, as in my example. Another approach would be to assign the handler lazily, as taquion suggests in his answer. I only prefer my answer because it's a common pattern, and I'd like other engineers to understand what is going on in my code, especially if it is critical for your application that the initialization logic execute at a specific time.


static public class MyClass
{
static bool _initialized = false;

static MyClass()
{
Console.WriteLine("Test.ctor called");
}

static void Initialize()
{
Console.WriteLine("Test.Initialize called");
_initialized = true;
}

static public event EventHandler MyEvent;

static public void RaiseMyEvent()
{
if (!_initialized) Initialize();
if (MyEvent != null) MyEvent(typeof(MyClass), new EventArgs());
}
}






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Comments

Popular posts from this blog

paramiko-expect timeout is happening after executing the command

Opening a url is failing in Swift

Export result set on Dbeaver to CSV