-1-
Der Titel mag ein wenig voreingenommen klingen, ist er auch, dennoch soll nachfolgend eine alternative Art des Event-Handlings gezeigt werden, die ganz ohne "event" keywords oder Delegates auskommt.
Warum überhaupt alternatives Event-Handling?
Weil die Art und Weise wie es aktuell gehandhabt wird (events, delegates und +=) insofern obsolet ist, dass diese sich nur sehr mühselig a) warten, b) verändern und c) isolieren lassen.
Ziel ist einer losere Kopplung, sodass im Idealfall Änderungen in einer Klasse (etwa Event-Sender) nicht auf Änderungen in vielen anderen Klassen auswirkt. Ferner soll das Event-Handling im Allgemeinen viel flexibler möglich sein, als es momentan der Fall ist.
Wie?
Ganz simpel: Dependency-Injection. Eine event-werfende Klasse muss eine Referenz auf ein bestimmtes Interface halten. Es ist nun die Aufgabe einer übergeordnete Manager-Klasse, diese Referenz zu setzen.
Möchte die Klasse nun ein Event werfen, muss diese lediglich die im Interface hinterlegte Methode (.Notify) aufrufen, und alle Klassen die Event-Empfänger sein wollen, werden benachrichtigt: Sender und Empfänger sind jeweils von einander entkoppelt.
Source
C#-Quellcode
- public sealed class NSEM
- {
- private static NSEM instance;
- public static NSEM Default()
- {
- if (instance == null)
- instance = new NSEM();
- return instance;
- }
- private Dictionary<Type, List<object>> typeInstances;
- private NSEM()
- {
- this.typeInstances = new Dictionary<Type, List<object>>();
- }
- public void Subscribe(object instance)
- {
- foreach (var irfc in Helper.GetNotificationReceivers(instance))
- {
- if (!typeInstances.ContainsKey(irfc))
- typeInstances.Add(irfc, new List<object>());
- typeInstances[irfc].Add(instance);
- }
- }
- public void Unsubscribe(object instance)
- {
- this.typeInstances[instance.GetType()].Remove(instance);
- }
- public IEnumerable<object> GetRelevantInstances<S>()
- {
- var instances = typeInstances[typeof(S)];
- for (int i = 0; i < instances.Count; i++)
- yield return instances[i];
- }
- public dynamic New<T>(params dynamic[]? arguments)
- {
- object[] injectedArguments = new object[arguments.Length + 1];
- injectedArguments[0] = new BasicNotificationAdapter<T>();
- Array.Copy(arguments, 0, injectedArguments, 1, arguments.Length);
- return Activator.CreateInstance(typeof(T), injectedArguments);
- }
- }
C#-Quellcode
- public static class Helper
- {
- private static string INTERFACE_NAME = typeof(INotificationReceiver<dynamic>).Name;
- public static IEnumerable<Type> GetNotificationReceivers(object instance)
- {
- Type type = instance.GetType();
- foreach (var irfc in type.GetInterfaces())
- {
- string name = irfc.Name;
- if (name.Equals(INTERFACE_NAME))
- yield return irfc;
- }
- }
- }
Das ist die so eben genannte Manager-Klasse. Soll eine neue event-werfende Klasse instanziiert werden, geschieht das über die Methode .New<T>().
Die notwendigen Interfaces sehen so aus:
Ein möglicher (zu "injizierender") INotificationAdapter kann etwa so aussehen:
Events lassen sich jetzt sehr leicht realisieren.
Beispiel Sender-Klasse:
Der Sender muss lediglich eine Referenz auf INotificationAdapter halten, über den Konstruktor wird diese dann von der Manager-Klasse instanziiert.
Möchte der Sender nun ein Event auslösen, so muss dieser jetzt .Notify() aufrufen und dann lediglich die Event-Meldung spezifizieren.
Beispiel Empfänger-Klassen:
C#-Quellcode
- public class TestSubscriberGuidListener : INotificationReceiver<Guid>
- {
- public TestSubscriberGuidListener()
- {
- NSEM.Default().Subscribe(this);
- }
- public void Notify(object sender, Guid argument)
- {
- Console.WriteLine("Notified with " + argument + " by " + sender);
- }
- }
- public class TestSubscriberIntListener : INotificationReceiver<int>
- {
- public TestSubscriberIntListener()
- {
- NSEM.Default().Subscribe(this);
- }
- public void Notify(object sender, int argument)
- {
- Console.WriteLine("Notified with " + argument + " by " + sender);
- }
- }
Im Konstruktor muss jetzt nur noch angegeben werden, dass man auf Events lauschen möchte, ferner die Interface implementiert und die auf die zu lauschende Meldung spezifiziert werden.
Die Ausführung sieht dann so aus:
C#-Quellcode
Die Empfänger-Klassen kennen nicht den Sender, der Sender kennt nicht die Empfänger. Änderungen auf einen, oder gar beiden Seiten rufen keine tiefergreifenden Nachwirkungen hervor.
Ergebnis:
Jener Empfänger, der auf Guid-artige Meldungen lauscht, empfängt die Guid-Meldung, analog empfängt jener anderer der auf int-artige Meldungen wartet, die int-Meldung.
_
Und Gott alleine weiß alles am allerbesten und besser.
Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „φConst“ ()