Blog Archives

Inversion of Control and Dependency Injection (advanced, Part 1), Programming Patterns

I finally had some time and spent some hours on Inversion of Control (IoC) and Dependency Injection (DI). When I did the same a few weeks ago I did not understand a lot. I got lost on the concept of inversion. I tried to figure out, what the inversion was. This blocked all and I ended up pretty much blank despite nearly an hour of pumping information into my head. When you get lost then you should do it at least properly and at full steam 😉  Well, I finally understood the headline … and the world started making sense again.

 

Why the name “Inversion of Control”? (IoC)

An entirely hardcoded class controls the program execution path. All branches are predetermined. By using interfaces you can decouple theses flows, so that at the time of creation the class does not exactly know what instructions to call next.
Let’s say you are using events. The class, which is raising an event, hands the control over to another class. The subscribing class is in control of the program flow, not the class that raises the event. The subscribing class can subscribe or unsubscribe. The class, which is providing the event, should not have any active control over subscriptions. Hence it is an inversion of control.
By using IoC classes become more encapsulated. Let’s say a perfect class is blind and urgently needs a guide dog. The control can now be taken over by external factors. This inverts the control entirely.
Unit testing uses that mechanism. A container can be used to control classes and change the bindings.

 

What is a container?

The expression “container” is rarely used in C#. The C++ world calls containers what C# calls collections. We have come across containers in my C++ posts. Follow the link for a quick refresher.
To simplify the matter we can call a UnityContainer a dictionary of objects with some additional methods.
This container performs binding between components. For instance it replaces all specific interface declarations by fully instantiated classes without the need to explicitly call any initialization.

 

What is dependency?

The structure of a program is: Input, Calculations, Output. The same generally applies to classes. Let’s say you want to run an analysis of a text file. That analysis class can only function properly if the required text file does exist. The calculations depend on the input. The same applies to the output, which can only run if the calculation was successful.

The input class calls the calculation class, which in turn calls the output class. As we are discussing inversion, let’s decouple the classes and implement events. In this case the output class subscribes to the calculation result event and the calculator subscribes to the input event.

 

What Dependency Injection? (DI)

“Dependency Injection” is a subset of “Inversion of Control”. IoC is a principle, whereas DI is an actual implementation of the principle.
DI is a software design pattern that allows removing hard-coded dependencies. First of all you do not create objects directly; you just describe how they look like. To accomplish this, the pattern uses interfaces or base classes. As the dependency is not hardcoded, the actual object can vary each time. It just needs to fulfill the inheritable pattern of an interface or base class.

public class Car { }   // base class
public class Audi : Car { }
public class BMW : Car { }
public class Exhibition {
   Car StageObject { set; get; }  // assign Audi or BMW as the dependency object of class Exhibition
}

 

Interfaces

What are the benefits of interfaces besides multiple inheritance? Several people can work on different problems simultaneously by using interfaces. One person for instance writes the code for logging to text files and another person writes the code for logging to databases. If both use the same interface definition, then the underlying classes can be easily replaced by each other. Interfaces are like promises to provide predefined class patterns at runtime.
Thus we end up with component separation, which is very useful in unit testing. Interfaces can eliminate unfathomable dependencies if used wisely.

 

Dependency Injection and Unit Testing

When you run unit tests, then you will need input data. But it could be too complex to run the entire program just to test some methods. You would try to only instantiate the minimum requirements. Think of a syringe and inject the data into the test case to create an acceptable environment. This can be difficult in case the program was not structured well. You need to examine the code to find dependencies.

Inversion of Control

 

I will cover some IoC/DI basics today and will follow-up on this after some other posts, which were queuing up in the last weeks:

  • Calling Java from C#
  • Calling C# from Java
  • Implement all Java source code examples of the 15 day series C# to C++
  • WPF Datagrid formatting
  • Google Authenticator
  • create a post index for my blog

 

Dependency Injection

There are several ways to implement dependencies in a class. The easiest way is to have a field that holds a reference to the dependency, which is probably the worst approach you can have in terms of flexibility.

public class Report1 {
 private IDataBase _DB = new DataBase();
} 

Report1 r1 = new Report1(); // dependency DataBase must be figured out by examining the code

 

You can use methods or properties to tell your classes what dependency objects they should use. The best approach though is via constructors. You can hardly miss parameters when trying to call the constructor. Of course bad constructor overloading can jeopardize this concept.

public class Car { }  // base class (instead of an interface)
public class Audi : Car { }
public class BMW : Car { }
public class Exhibition {
   Car StageObject { set; get; }  // assign Audi or BMW as the dependency object of class Exhibition
}

public class Report2 {
   public IDataBase DB { get; private set; }
   public void SetDB(IDataBase xDB) { DB = xDB; }
} 

public class Report3 {
  public IDataBase DB { get; set; }
} 

public class Report4 {
   private IDataBase _DB;
   public Report4(IDataBase xDB) { _DB = xDB; }
} 

DataBase lDB = new DataBase();
Report2 r2 = new Report2(); r2.SetDB(lDB);
Report3 r3 = new Report3(); r3.DB = lDB;
Report4 r4 = new Report4(lDB); 

 
If the world was just like class Report4, then we could more or less end the post here. Unfortunately dependencies are often not that obvious. They are well hidden and you need to read the code thoroughly to build unit tests.
Dependency Injection goes further and the real take off takes place with Moq, which I will explain in the follow-up post.

The following code example was didactically compiled. You don’t need any further information, it should be self-explanatory. You can download unity by following this link or using NuGet.

using System;
using Microsoft.Practices.Unity;

public interface IDataBase {
   void QuerySomething();
} // interface

public interface ITextFile {
   void LoadSomething();
} // interface

public interface INetwork {
   string Text { set; get; }
   void ReceiveSomething();
} // interface

public class Network : INetwork {
   public void ReceiveSomething() { Console.WriteLine("Receiving TCP data ..."); }
   public string Text { set; get; }
} // class

public class DataBase : IDataBase {
   private string _Dummy = "I am doing something.";
   public void QuerySomething() { Console.WriteLine(_Dummy); }
} // class

public class TextFile1 : ITextFile {
   public void LoadSomething() { Console.WriteLine("TF1: Loading something..."); }
} // class

public class TextFile2 : ITextFile {
   public void LoadSomething() { Console.WriteLine("TF2: Loading something..."); }
} // class

public class TextFile3 : ITextFile {
   public void LoadSomething() { Console.WriteLine("TF3: Loading something..."); }
} // class

public class Report5 {
   public string Text = "#N/A";
   private IDataBase _DB;
   public readonly ITextFile TF;
   public IDataBase getDB() { return _DB; }
   public Report5(IDataBase xDB, ITextFile xTextFile) { _DB = xDB; TF = xTextFile; }
} // class

public class Report6 {
   public readonly string Text1;
   public readonly string Text2;
   private readonly ITextFile _TextFile;
   public readonly INetwork Network;
   public Report6(ITextFile xTextFile, INetwork xNetwork, string xText1, string xText2) {
      _TextFile = xTextFile;
      Network = xNetwork;
      Text1 = xText1;
      Text2 = xText2;
   } // constructor
} // class

class Program {

   static void Main(string[] args) {

      UnityContainer lContainer = new UnityContainer(); // using Microsoft.Practices.Unity;  

      Report5 r;

      // insufficient data
      Console.WriteLine("test: insufficient data");
      Console.WriteLine("Registering IDataBase");
      lContainer.RegisterType(typeof(IDataBase), typeof(DataBase)); // whenever someone asks for an IDataBase, then return a new DataBase instance    
      try {
         r = lContainer.Resolve<Report5>(); // throws an exception, because ITextFile is undefined
      }
      catch (Exception ex) { Console.WriteLine(ex.Message); }
      Console.WriteLine();

      // full data
      Console.WriteLine("test: sufficient data");
      Console.WriteLine("Registering ITextFile TF1");
      Console.WriteLine("IDataBase is still registered");
      lContainer.RegisterType(typeof(ITextFile), typeof(TextFile1));
      r = lContainer.Resolve<Report5>();
      Console.WriteLine("type of r.TF is " + r.TF.GetType()); // TextFile1
      r.getDB().QuerySomething();
      r.TF.LoadSomething(); // this is TextFile1
      Console.WriteLine();

      // override a previous type registration with another type
      Console.WriteLine("test: override a previous type registration with another type");
      Console.WriteLine("Registering ITextFile TF2");
      lContainer.RegisterType(typeof(ITextFile), typeof(TextFile2)); // override the first type registration    
      //lContainer.RegisterType<ITextFile, TextFile2>();   // same as lContainer.RegisterType(typeof(ITextFile), typeof(TextFile2));
      r = lContainer.Resolve<Report5>();
      Console.WriteLine("type of r.TF is " + r.TF.GetType()); // TextFile2
      r.getDB().QuerySomething();
      r.TF.LoadSomething(); // this is TextFile2 
      Console.WriteLine();

      // override a previous type registration with an instance
      Console.WriteLine("test: override a previous type registration with an instance");
      Console.WriteLine("Registering an instance of TextFile3");
      ITextFile lTextFile = new TextFile3();
      lContainer.RegisterInstance(lTextFile);
      r = lContainer.Resolve<Report5>();
      Console.WriteLine("type of r.TF is " + r.TF.GetType()); // TextFile3
      r.getDB().QuerySomething();
      r.TF.LoadSomething(); // this is TextFile3 
      Console.WriteLine();

      // using names to register instances
      Console.WriteLine("test: using names to register instances");
      lContainer.RegisterType<Report5, Report5>(); // creates a default class without any name
      Report5 a = new Report5(r.getDB(), r.TF); lContainer.RegisterInstance("A", a); a.Text = "Report A";
      Report5 b = new Report5(r.getDB(), r.TF); lContainer.RegisterInstance("B", b); b.Text = "Report B";

      r = lContainer.Resolve<Report5>("A"); Console.WriteLine("got " + r.Text);
      r = lContainer.Resolve<Report5>("B"); Console.WriteLine("got " + r.Text);
      r = lContainer.Resolve<Report5>(); Console.WriteLine("got " + r.Text); r.Text = "same instance?";
      r = lContainer.Resolve<Report5>("X"); Console.WriteLine("got " + r.Text);  // new instance
      r = lContainer.Resolve<Report5>(); Console.WriteLine("got " + r.Text); // new instance
      Console.WriteLine();

      // => HAVE A LOOK AT THE BELOW CONTAINER SNAPSHOT => there are 3 instances for Report5

      // using names to register instances
      Console.WriteLine("test: revision, using the same instance");
      Console.WriteLine("ContainerControlledLifetimeManager: re-use instances (singleton behaviour for objects)");
      lContainer.RegisterType<Report5, Report5>(new ContainerControlledLifetimeManager());
      r = lContainer.Resolve<Report5>(); Console.WriteLine("got " + r.Text); r.Text = "same instance?";
      r = lContainer.Resolve<Report5>("X"); Console.WriteLine("got " + r.Text);  // new instance
      r = lContainer.Resolve<Report5>(); Console.WriteLine("got " + r.Text); // same instance !!!!!
      Console.WriteLine();

      // constructors with parameters
      lContainer.RegisterType<INetwork, Network>();
      lContainer.RegisterType<ITextFile, TextFile2>();
      Console.WriteLine("test: constructors with parameters");
      ResolverOverride[] lParameters = new ResolverOverride[] { new ParameterOverride("xText1", "Hello "), new ParameterOverride("xText2", "world") };
      Report6 lReport6 = lContainer.Resolve<Report6>(lParameters);
      Console.WriteLine("Report6 text field values are: " + lReport6.Text1 + lReport6.Text2);

      Console.ReadLine();
   } //

} // class

 

instances
 

example output:
test: insufficient data
Registering IDataBase
Resolution of the dependency failed, type = “Report5”, name = “(none)”.
Exception occurred while: while resolving.
Exception is: InvalidOperationException – The type ITextFile does not have an ac
cessible constructor.
———————————————–
At the time of the exception, the container was:

Resolving Report5,(none)
Resolving parameter “xTextFile” of constructor Report5(IDataBase xDB, ITextFil
e xTextFile)
Resolving ITextFile,(none)

test: sufficient data
Registering ITextFile TF1
IDataBase is still registered
type of r.TF is TextFile1
I am doing something.
TF1: Loading something…

test: override a previous type registration with another type
Registering ITextFile TF2
type of r.TF is TextFile2
I am doing something.
TF2: Loading something…

test: override a previous type registration with an instance
Registering an instance of TextFile3
type of r.TF is TextFile3
I am doing something.
TF3: Loading something…

test: using names to register instances
got Report A
got Report B
got #N/A
got #N/A
got #N/A

test: revision, using the same instance
ContainerControlledLifetimeManager: re-use instances (singleton behaviour for ob
jects)
got #N/A
got #N/A
got same instance?

test: constructors with parameters
Report6 text field values are: Hello world

Events (part 3, advanced)

Events: Let’s go multithreading! We want the crème de la crème. Well, multitasking is also fine.

The code does not need to run in a specific sequence. The required independence is given. Again, there are many ways to use multithreading. An easy approach is to start a task in each method that is called by the event.

C# does support BeginInvoke() for delegates. This method is not supported by the .NET Compact Framework though. We don’t care, because our hardcore programs are for serious applications, definitely not for mobile phone apps. Let’s see how good BeginInvoke() works. Maybe we don’t have to reinvent the wheel.

BeginInvoke() initiates asynchronous calls, it returns immediately and provides the IAsyncResult, which can be used to monitor the progress of the asynchronous call.
EndInvoke() retrieves the results. It blocks until the thread has completed.

You have the following options after calling BeginInvoke():
1) Call EndInvoke() to block the current thread until the call completes.
2) Obtain the WaitHandle from IAsyncResult.AsyncWaitHandle, use its WaitOne() and then call EndInvoke().
3) Poll the IAsyncResult to check the current state, after it has completed call EndInvoke().
4) Pass a callback to BeginInvoke(). The callback will use the ThreadPool to notify you. In the callback you have to call EndInvoke().

public event EventHandler OnChange;

public void DoSomeWork(object sender, object e) {
    Thread.Sleep(2100);
    Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " mission accomplished!");
} //

public void RunMyExample() {
    OnChange += new EventHandler(DoSomeWork);
    //OnChange += new EventHandler(DoSomeWork);
    //OnChange += new EventHandler(DoSomeWork);

    IAsyncResult lAsyncResult;

    Console.WriteLine("Choice 1");
    lAsyncResult = OnChange.BeginInvoke(this, EventArgs.Empty, null, null);
    OnChange.EndInvoke(lAsyncResult);

    Console.WriteLine("Choice 2");
    lAsyncResult = OnChange.BeginInvoke(this, EventArgs.Empty, null, null);
    lAsyncResult.AsyncWaitHandle.WaitOne();

    Console.WriteLine("Choice 3");
    lAsyncResult = OnChange.BeginInvoke(this, EventArgs.Empty, null, null);
    while (!lAsyncResult.IsCompleted) {
        Thread.Sleep(500);
        Console.WriteLine("work still not completed :(");
    }

    Console.WriteLine("Choice 4"); 
    OnChange.BeginInvoke(this, EventArgs.Empty, (xAsyncResult) => {
            Console.WriteLine("callback running");
            OnChange.EndInvoke(xAsyncResult);
        }, null);

    Console.WriteLine("press return to exit the program");
    Console.ReadLine();
}//    

example output:
Choice 1
Thread 6 mission accomplished!
Choice 2
Thread 6 mission accomplished!
work still not completed 😦
work still not completed 😦
work still not completed 😦
work still not completed 😦
Thread 10 mission accomplished!
work still not completed 😦
press return to exit the program
Thread 6 mission accomplished!
callback running

After having lived such a nice life, we have to face a major issue with BeginInvoke(). Uncomment “//OnChange += new EventHandler(DoSomeWork);”, don’t get annoyed now!
You will get an error message saying

“The delegate must have only one target”.

Although the delegate class can deal with multiple targets, asynchronous calls accept just one target.

So let’s try something else.

    public event EventHandler OnChange;

    public void DoSomeWork(object sender, object e) {
        Thread.Sleep(2100);
        Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " mission accomplished!");
    } //

    public void RunMyExample() {
        OnChange += new EventHandler(DoSomeWork);
        OnChange += new EventHandler((sender, e) => { throw new Exception("something went wrong"); });
        OnChange += new EventHandler(DoSomeWork);

        EventHandler lOnChange = OnChange;
        if (lOnChange == null) return;   // just to demonstrate the proper way to call events, not needed in this example                
        foreach (EventHandler d in lOnChange.GetInvocationList()) {
            Task lTask = Task.Factory.StartNew(() => d(this, EventArgs.Empty));
            lTask.ContinueWith((i) => { Console.WriteLine("Task canceled"); }, TaskContinuationOptions.OnlyOnCanceled);
            lTask.ContinueWith((i) => { Console.WriteLine("Task faulted"); }, TaskContinuationOptions.OnlyOnFaulted);
            lTask.ContinueWith((i) => { Console.WriteLine("Task completion"); }, TaskContinuationOptions.OnlyOnRanToCompletion);
        }

        Console.WriteLine("press return to exit the program");
        Console.ReadLine();
    }//    

It seems that Microsoft has a serious bug here.This code does not execute properly each time. I guess it has to do with the asynchronous behaviour of StartNew(). It sometimes calls the wrong method DoSomeWork() three times and does not raise the exception.
It seems foreach overrides variable “d” before it is inserted in StartNew(). With a little tweak we can avoid this bug. We simply assign “d” to a new local variable. That way we have unique copies of “d”. Weird stuff, but that is the life of a coder sometimes.

public event EventHandler OnChange;

public void DoSomeWork(object sender, object e) {
    Thread.Sleep(2100);
    Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " mission accomplished!");
} //

public void RunMyExample() {
    OnChange += new EventHandler(DoSomeWork);
    OnChange += new EventHandler((sender, e) => { throw new Exception("something went wrong"); });
    OnChange += new EventHandler(DoSomeWork);

    EventHandler lOnChange = OnChange;
    if (lOnChange == null) return;   // just to demonstrate the proper way to call events, not needed in this example                
    foreach (EventHandler d in lOnChange.GetInvocationList()) {
        EventHandler e = d;
        Task lTask = Task.Factory.StartNew(() => e(this, EventArgs.Empty));
        lTask.ContinueWith((i) => { Console.WriteLine("Task canceled"); }, TaskContinuationOptions.OnlyOnCanceled);
        lTask.ContinueWith((i) => { Console.WriteLine("Task faulted"); }, TaskContinuationOptions.OnlyOnFaulted);
        lTask.ContinueWith((i) => { Console.WriteLine("Task completion"); }, TaskContinuationOptions.OnlyOnRanToCompletion);
    }

    Console.WriteLine("press return to exit the program");
    Console.ReadLine();
}//    

Example output:
press return to exit the program
Thread 11 mission accomplished!
Thread 10 mission accomplished!
Task completion
Task faulted
Task completion

This tiny tweak worked. You just have to know the compiler bugs. I hope this little notice saves you at least 3 hours of work.
You probably remember that we faced a similar issue in my post “Exiting Tasks (advanced)” https://csharphardcoreprogramming.wordpress.com/2013/12/11/exiting-tasks/ . I would suggest to only use Task.Factory.StartNew() with caution. Maybe it only happens in conjunction with lambda expressions.
The following code is also running very well. The variable “d” is not used directly in Task.Factory.StartNew().

...
 foreach (EventHandler d in lOnChange.GetInvocationList()) {
            Action a = () => d(this, EventArgs.Empty);
            Task lTask = Task.Factory.StartNew(a);
            lTask.ContinueWith((i) => { Console.WriteLine("Task canceled"); }, TaskContinuationOptions.OnlyOnCanceled);
            lTask.ContinueWith((i) => { Console.WriteLine("Task faulted"); }, TaskContinuationOptions.OnlyOnFaulted);
            lTask.ContinueWith((i) => { Console.WriteLine("Task completion"); }, TaskContinuationOptions.OnlyOnRanToCompletion);
        }
...

Events (part 2, advanced)

We are going to construct our custom event accessor now. It deals with additions and removals of subscriptions. Accessors do pretty much look like property definitions. But instead of set and get you have to use add and remove.

public class MyActionEvent4 {
    private object _Lock = new object();            // a new object simply to avoid lock conflicts
    private event EventHandler<MyArgs> _OnChange;
    private event EventHandler<MyArgs> OnChange {
        add {
            lock (_Lock) { _OnChange += value; }
        }
        remove {
            lock (_Lock) { _OnChange -= value; }
        }
    } //

    public void RaiseEvent() {
        lock (_Lock) {
            EventHandler<MyArgs> lHandler = _OnChange;
            if (lHandler == null) return;
            lHandler(this, new MyArgs(0));   
        }        
    }//
} // class

Now we have one big problem here. The RaiseEvent() method has to obtain a lock each time, which causes a serious impact on time sensitive programs. Luckily you do not need to care about changing subscriptions during the invocation. Delegate references are thread-safe, because they are immutable like strings. Let’s simply take the lock out of the RaiseEvent() method, et voilà!

public class MyActionEvent5 {
    private object _Lock = new object();
    private event EventHandler<MyArgs> _OnChange;
    private event EventHandler<MyArgs> OnChange {
        add {
            lock (_Lock) { _OnChange += value; }
        }
        remove {
            lock (_Lock) { _OnChange -= value; }
        }
    } //

    public void RaiseEvent() {
        EventHandler<MyArgs> lHandler = _OnChange;
        if (lHandler == null) return;
        lHandler(this, new MyArgs(0));   
    }//
} // class

It is clear that events are not delegates. They restrict access rights from outside of the event class. To describe events you could most likely say that they are wrappers around delegates.

Whenever an exception is thrown during an event call then all following calls will not be executed. And it is tricky to determine which calls were not executed, because the order of event calls is not guaranteed to be in sequence. In fact it does execute in sequence, but there is no guarantee.

static void EventExceptions1() {
    MyActionEvent3 lEvent = new MyActionEvent3();
    lEvent.OnChange += (sender, e) => Console.WriteLine("Executed subscription 1");
    lEvent.OnChange += (sender, e) => { throw new Exception("OMG!"); };
    lEvent.OnChange += (sender, e) => Console.WriteLine("Executed subscription 3");
    lEvent.RaiseEvent();
} //

So you have to deal with exceptions manually if you want to satisfy/execute as many event subscriptions as possible. You could add a try/catch block for each subscription. Or you could invoke the InvocationList yourself by calling the GetInvocationList() method [System.Delegate] and execute each item manually in a try/catch block. Let’s have a look at the following practical solution:

public class MyActionEvent6 {
    public event EventHandler OnChange = delegate { };

    public void RaiseEvent() {
        List<Exception> lExceptions = new List<Exception>();

        foreach (Delegate lHandler in OnChange.GetInvocationList()) {
            try { lHandler.DynamicInvoke(this, EventArgs.Empty); }
            catch (Exception ex) { lExceptions.Add(ex); }
        }

        if (lExceptions.Count > 0) throw new AggregateException(lExceptions);
    }//
} // class

static void EventExceptions6() {
    MyActionEvent6 lEvent = new MyActionEvent6();
    lEvent.OnChange += (sender, e) => Console.WriteLine("Executed subscription 1");
    lEvent.OnChange += (sender, e) => { throw new Exception("OMG!"); };
    lEvent.OnChange += (sender, e) => Console.WriteLine("Executed subscription 3");

    try { lEvent.RaiseEvent(); }
    catch (AggregateException ex) {
        foreach (Exception lException in ex.InnerExceptions) {
            Console.WriteLine(lException.InnerException.Message);
        }
    }
} //

Events (part 1, advanced)

Events link code dynamically together via subscriptions. There are many ways to achieve this. To better understand events, we will start with the old school approach. It is pretty much Java style. You would not do such in C#.

public interface IListener {
    void OnEvent(int xDummy);
} // interface

public class AnyClass {
    private List<Ilistener> _Listeners = new List<Ilistener>();

    public void AddListener(IListener xListener) {
        lock (_Listeners) { _Listeners.Add(xListener); }
    } //

    public void RemoveListener(IListener xListener) {
        lock (_Listeners) { _Listeners.Remove(xListener); }
    } //

    protected void RaiseEvent() {
        lock (_Listeners) {
            foreach (IListener lListener in _Listeners) lListener.OnEvent(0);
        }
    }//

    public void DoSomeCalc() {
        Console.WriteLine("calculating something");
        Console.WriteLine("done");
        Console.WriteLine("going to tell others");
        RaiseEvent();
    } //
} // class

public class MyLittleProgram : IListener {
    public void StartDemo() {
        AnyClass c = new AnyClass();
        c.AddListener(this);
        c.DoSomeCalc();
        c.RemoveListener(this);
    } //

    public void OnEvent(int xDummy) {
        Console.WriteLine("Hey, cool! I got a notification");
    } //
} // class

static void Main(string[] args) {    
    MyLittleProgram p = new MyLittleProgram();
    p.StartDemo();

    Console.ReadLine();
} //

Above example program uses a List that stores classes. An event is raised by iterating through that list and calling the desired method. This is a lot of code for something really simple.
In the post “Delegates (basics)” https://csharphardcoreprogramming.wordpress.com/2013/12/16/delegates-basics/ we have learned to use delegates. And let’s emphasize it again: We are talking about MulticastDelegates. In the post “Lambda expressions (advanced)” https://csharphardcoreprogramming.wordpress.com/2013/12/17/lambda-expressions-advanced/ we then came across Func and Action. The next step would be to make use of Action.

public class MyActionEvent {
    public Action OnChange { get; set; }

    public void RaiseEvent() {
        Action lAction = OnChange;
        if (lAction == null) return;
        lAction();
    }//
} // class

static void Main(string[] args) {
    MyActionEvent lEventClass = new MyActionEvent();
    lEventClass.OnChange += () => Console.WriteLine("I got a notification.");
    lEventClass.OnChange += () => Console.WriteLine("Me too.");
    lEventClass.RaiseEvent();

    Console.ReadLine();
} //

Notice that we used a local delegate variable (Action lAction = OnChange;) and did not call OnEvent directly. This is important in a multithreaded environment. Allthough it is unlikely, the event could still turn null before it gets raised.

Above code is much more legible now. But it is really bad practice. The event is accessible from outside. The class MyActionEvent loses control over the event. The Action OnChange is accessible and can be raised from outside the class. This is why C# implemented the “event” keyword. Code no longer uses public properties but public event fields, which are properly protected.
An event can only be assigned by using “+=”, whereas an Action can also be entirely overridden with “=”. It makes programming a little bit safer, you don’t run the risk of removing all previous subscriptions by mistake. Also events can only be raised by code within the same class. There is no way events could be raised from outside.

To make your life easier you can assign an empty delegate during the initialization process. By using “= delegate { };” there is no further need to check for the null condition, especially that no outside code can assign null to the event. Only code inside the class can assign null. Therefore the class is in full control of the event.

public class MyActionEvent2 {
    public event Action OnChange = delegate { };

    public void RaiseEvent() {
        OnChange();
        Console.WriteLine("number of event handlers: " + OnChange.GetInvocationList().Length);
    }//
} // class

Personally I do not like the approach with empty delegates. The number of delegates is artificially increased by one. A null check is pretty much using no time at all, so why should someone prefer to call an empty method each time an event is raised?

The next step is to replace the dirty Action by a proper delegate definition, which is EventHandler<>. By default its parameters are the sender object and some event arguments. This is a C# convention and should be followed. Surely you are familiar with such event calls from the Winforms/WPF environment.

public class MyArgs : EventArgs {
    public MyArgs(int xValue) {
        Value = xValue;
    } // constructor

    public int Value { get; set; }
} // class

public class MyActionEvent3 {
    public event EventHandler<myargs> OnChange;

    public void RaiseEvent() {
        EventHandler<myargs> lHandler = OnChange;
        if (lHandler == null) return;
        lHandler(this, new MyArgs(0));                
    }//
} // class

static void Main(string[] args) {
    MyActionEvent3 lEventClass = new MyActionEvent3();
    lEventClass.OnChange += (sender, e) => Console.WriteLine("Event raised, field value is: " + e.Value);            
    lEventClass.RaiseEvent();
    
    Console.ReadLine();
} //