I am just looking into implementing an MVVMCross Messenger solution that will enable me to upload information to Google Analytics when published from either the iOS application or the PCL.
The problem I have the is that the subscription delgates are not fired after I publish. Can you subscribe to MVVMCross Messenger subscriptions from a static class?
Subscriptions in static class
public static class GoogleAnalyticsWrapper //: IDisposable
{
private const string TrackingId = "xxxxxxxxxxx";
private static readonly IMvxMessenger messenger;
private static readonly MvxSubscriptionToken screenNameToken;
private static readonly MvxSubscriptionToken eventToken;
private static readonly MvxSubscriptionToken exceptionToken;
private static readonly MvxSubscriptionToken performanceToken;
private static readonly MvxSubscriptionToken publishToken;
private static bool disposed = false;
private static SafeHandle handle;
static GoogleAnalyticsWrapper()
{
Gai.SharedInstance.DispatchInterval = 60;
Gai.SharedInstance.TrackUncaughtExceptions = true;
Gai.SharedInstance.GetTracker(TrackingId);
messenger = new MvxMessengerHub();// Mvx.Resolve<IMvxMessenger>();
screenNameToken = messenger.Subscribe<GaScreenNameMessage>((m) => SetScreenName(m));
int count = messenger.CountSubscriptionsFor<GaScreenNameMessage>();
eventToken = messenger.Subscribe<GaEventMessage>(CreateEvent);
exceptionToken = messenger.Subscribe<GaExceptionMessage>(CreateException);
performanceToken = messenger.Subscribe<GaPerformanceTimingMessage>(CreatePerformanceMetric);
publishToken = messenger.Subscribe<GaPublishMessage>(PublishAll);
}
public static string Dummy { get; set; }
public static void SetScreenName(GaScreenNameMessage message)
{
System.Diagnostics.Debugger.Break();
Gai.SharedInstance.DefaultTracker.Set(GaiConstants.ScreenName, message.ScreenName);
Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateScreenView().Build());
}
public static void CreateEvent(GaEventMessage message)
=> Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateEvent(message.Category, message.Action, message.Label, message.Number).Build());
private static void CreateException(GaExceptionMessage message)
=> Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateException(message.ExceptionMessage, message.IsFatal).Build());
private static void CreatePerformanceMetric(GaPerformanceTimingMessage message)
=> Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateTiming(message.Category, message.Milliseconds, message.Name, message.Label).Build());
private static void PublishAll(GaPublishMessage message)
=> Gai.SharedInstance.Dispatch();
public static void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources.
if (handle != null)
{
handle.Dispose();
}
}
// Dispose unmanaged managed resources.
disposed = true;
}
}
}
Publication
messengerService.Publish<GaEventMessage>(new GaEventMessage(this, "Event", "Publish Event", "Publish Event From First View Model", 123));
The problem is, that you are creating a new MvxMessengerHub in your static class, but (I guess) inject IMvxMessenger in your consuming classes, which is created by MvvMCross during the initialization lifecycle and so a different instance.
The easy solution would be to initialize it in your App.cs like
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
public override void Initialize()
{
// ...
var m = Cirrious.CrossCore.Mvx.Resolve<IMvxMessenger>();
GoogleAnalyticsWrapper.Initialize(m);
// ...
}
}
With a wrapper like this
public static class GoogleAnalyticsWrapper
{
static void Initialize(IMvxMessenger messenger)
{
Gai.SharedInstance.DispatchInterval = 60;
Gai.SharedInstance.TrackUncaughtExceptions = true;
Gai.SharedInstance.GetTracker(TrackingId);
screenNameToken = messenger.Subscribe<GaScreenNameMessage>((m) => SetScreenName(m));
int count = messenger.CountSubscriptionsFor<GaScreenNameMessage>();
eventToken = messenger.Subscribe<GaEventMessage>(CreateEvent);
exceptionToken = messenger.Subscribe<GaExceptionMessage>(CreateException);
performanceToken = messenger.Subscribe<GaPerformanceTimingMessage>(CreatePerformanceMetric);
publishToken = messenger.Subscribe<GaPublishMessage>(PublishAll);
}
// ...
}
Advanced Hint
But as far as I see, you don't even need messaging for this case, because it's one to one "communication". I think it would be nice, if you move the functionality of your GoogleAnalyticsWrapper into a well defined Service like:
interface ITrackingService
{
void SetScreenName(GaScreenNameMessage message);
void CreateEvent(GaEventMessage message);
void CreateException(GaExceptionMessage message);
void CreatePerformanceMetric(GaPerformanceTimingMessage message);
void PublishAll(GaPublishMessage message);
}
public class GoogleAnalyticsTrackingService : ITrackingService
{
private const string TrackingId = "xxxxxxxxxxx";
public GoogleAnalyticsTrackingService()
{
Gai.SharedInstance.DispatchInterval = 60;
Gai.SharedInstance.TrackUncaughtExceptions = true;
Gai.SharedInstance.GetTracker(TrackingId);
}
public void SetScreenName(GaScreenNameMessage message)
{
Gai.SharedInstance.DefaultTracker.Set(GaiConstants.ScreenName, message.ScreenName);
Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateScreenView().Build());
}
public void CreateEvent(GaEventMessage message)
{
Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateEvent(message.Category, message.Action, message.Label, message.Number).Build());
}
private void CreateException(GaExceptionMessage message)
{
Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateException(message.ExceptionMessage, message.IsFatal).Build());
}
private void CreatePerformanceMetric(GaPerformanceTimingMessage message)
{
Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateTiming(message.Category, message.Milliseconds, message.Name, message.Label).Build());
}
private void PublishAll(GaPublishMessage message)
{
Gai.SharedInstance.Dispatch();
}
}
That has to be registered in your App
Mvx.LazyConstructAndRegisterSingleton<ITrackingService, GoogleAnalyticsTrackingService>();
And can be consumed with constructor injection or manual resolves
class MyViewModel : MvxViewModel
{
public MyViewModel(ITrackingService tracking)
{
tracking.CreateEvent(new GaEventMessage(this, "Event", "Publish Event", "Publish Event From First View Model", 123));
}
}
// or
class MyViewModel : MvxViewModel
{
public MyViewModel()
{
var tracking = Mvx.Resolve<ITrackingService>();
tracking.CreateEvent(new GaEventMessage(this, "Event", "Publish Event", "Publish Event From First View Model", 123));
}
}
There is still one Problem: The interface has still a dependency to google analytics. But the dependency can be easily removed by using multiple parameters instead of a parameter object.
interface ITrackingService
{
void CreateEvent(string eventName, string title, string message, params object[] additionalParams);
// ...
}
// call:
tracking.CreateEvent("Event", "Publish Event", "Publish Event From First View Model", 123);
With this, you are able to unit test it and exchange the tracking service with litte effort, if your stakeholders decide to switch to adobe omniture or whatever.
Related
Help!
I can not figure it out how to implement Serilog to output my logs in real time into a textbox from Winforms.
I have an application written in .Net C# that was written a long time ago and had the logging framework log4net. I had different appenders and one was created in my code :
public class ExAppender : AppenderSkeleton{
private IExAppender control = null;
public void AttachControl(IExAppender obj)
{ this.control = obj;}
protected override void Append(LoggingEvent loggingEvent)
{
try
{
string message = RenderLoggingEvent(loggingEvent);
if (this.control != null)
{
this.control.LogMessage(message, loggingEvent.Level.Name);
}
}catch{// ignore}
}
And after that I had another class defined ExLogger:
public static class ExLogger
{ private static readonly ILog LoggerObj = null;
public static bool AttachControl(IExAppender obj)
{
IAppender[] appenders = LoggerObj.Logger.Repository.GetAppenders();
(appender as ExAppender).AttachControl(obj);
return true;
}
return false;}
I defined my serilog loggers in app.config, I want to read them from there because i have multiple loggers, I think that I need to use public class ExAppender : ILogEventSink, I replaced the old code to be suitable for Serilog, it writes to files, to eventLog, console etc, BUT I could not found a way to attach a windows to the logger and to write there. After my modification I obtaind something like this:
public class ExAppender : ILogEventSink
{
public ExAppender control = null;
public ConcurrentQueue<string> Events { get; } = new ConcurrentQueue<string>();
public void AttachControl(IExAppender obj)
{
this.control = obj;
}
public void Emit(LogEvent logEvent)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
var renderSpace = new ExAppender();
Events.Enqueue(renderSpace.ToString());
try
{ string message = logEvent.RenderMessage();
if (this.control != null)
{
this.control.LogMessage(message, logEvent.Level.ToString());
}
}catch { }
}
And for the ExLogger class:
public static bool AttachControl( IExAppender obj)
{try
{
ILogger test = new LoggerConfiguration()
.ReadFrom.AppSettings(settingPrefix: "ExAppender")
.WriteTo.ExAppender(restrictedToMinimumLevel: LogEventLevel.Information)
.CreateLogger();
return true;
}catch
{
return false;
}}
Can someone guide me? Does someone has an example or maybe explain what am I missing?
Maybe I am a bit too late to help you, but this is how I implemented it:
Custom logger sink, which has EventHandler:
public class TbsLoggerSink : ILogEventSink
{
public event EventHandler NewLogHandler;
public TbsLoggerSink() { }
public void Emit(LogEvent logEvent)
{
#if DEBUG
Console.WriteLine($"{logEvent.Timestamp}] {logEvent.MessageTemplate}");
#endif
NewLogHandler?.Invoke(typeof(TbsCore.Helpers.TbsLoggerSink), new LogEventArgs() { Log = logEvent });
}
}
public class LogEventArgs : EventArgs
{
public LogEvent Log { get; set; }
}
When creating the Serilog logger, add your custom sink. I use static sink/logger so I can access it from anywhere.
public static TbsLoggerSink LoggerSink = new TbsLoggerSink();
public static readonly Serilog.Core.Logger Log = new LoggerConfiguration()
.WriteTo.Sink(LoggerSink)
.CreateLogger();
Than in your view/form, where you have TextBox/RichTextBox (in my case this.logTextBox), add event handler:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
Utils.LoggerSink.NewLogHandler += LogHandler;
}
private void LogHandler(object sender, EventArgs e)
{
var log = ((LogEventArgs)e).Log;
this.logTextBox.Text = $"{log.Timestamp.DateTime.ToString("HH:mm:ss")}: {log.MessageTemplate}\n{this.logTextBox.Text}";
}
}
I want to implement delegation pattern using delegates
public class Cat {
private delegate void SoundDelegate();
private SoundDelegate sound;
public Cat() {
sound = new SoundDelegate(SomeClass.DoSound1);
}
public void DoSound() {
sound();
}
}
public class PussyCat {
private delegate void SoundDelegate();
private SoundDelegate sound;
public PussyCat() {
sound = new SoundDelegate(SomeClass.DoSound2);
}
public void DoSound() {
sound();
}
}
public class SomeClass {
public static void DoSound1() {
Console.WriteLine("Sound 1");
}
public static void DoSound2() {
Console.WriteLine("Sound 2");
}
}
Does this code impelement the delegation pattern? I mean can I use delegates for implement delegation pattern or this way is incorrect.
And if the previous example is correct and I can use delegates to implement the delegation pattern and implement the observer pattern, then what is the difference between the observer pattern and the delegation pattern and what is similar?
The difference between delegation and observer patterns is a level of control your class has over the delegate/observer.
In case of delegate, it's assumed that your class has full control over how delegated class should be used. The observable class has no idea of how exactly it would be used by other classes.
It's also often assumed that observable class could have any number of observers while delegate is usually one.
I also simplified the code provided trying to avoid unnecessarily class PussyCat, so the original class could be configured to use any delegate in runtime.
You can also find CatObservable class to understand the idea of observable-observer implementation.
class Program
{
static void Main(string[] args)
{
Cat cat1 = new Cat(SomeClass.DoSound1);
Cat cat2 = new Cat(SomeClass.DoSound2);
CatObservable cat3 = new CatObservable();
cat3.Sound += Cat3_Sound;
cat3.Sound += (object sender, EventArgs e) => { SomeClass.DoSound1(); } ;
cat3.Sound += (object sender, EventArgs e) => { SomeClass.DoSound2(); };
}
private static void Cat3_Sound(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
public class Cat
{
public delegate void SoundDelegate();
public SoundDelegate Sound { get; set; }
public Cat(SoundDelegate soundDelagate)
{
Sound = soundDelagate;
}
protected void DoSound()
{
if (Sound!=null)
Sound();
}
}
public class CatObservable
{
public event EventHandler Sound;
public CatObservable()
{
}
protected void DoSound()
{
if (Sound != null)
Sound(this, EventArgs.Empty);
}
}
public class SomeClass
{
public static void DoSound1()
{
Console.WriteLine("Sound 1");
}
public static void DoSound2()
{
Console.WriteLine("Sound 2");
}
}
I am writing unit tests for my code. And using 'Microsoft.VisualStudio.TestTools'.
I do not want to popup a message while running 'Unit Test' for functions which contains message boxes. I am able to do this by using following code,
public static class UnitTestDetector
{
static UnitTestDetector()
{
string testAssemblyName = "Microsoft.VisualStudio.QualityTools.UnitTestFramework";
UnitTestDetector.IsInUnitTest = AppDomain.CurrentDomain.GetAssemblies()
.Any(a => a.FullName.StartsWith(testAssemblyName));
}
public static bool IsInUnitTest { get; private set; }
}
But for this solution I have to use 'IsInUnitTest' in actual function to disable message boxes. Is there any other solution?
Yes.
Create interface for displaying messages
public interface IDisplay
{
void ShowMessage(string message);
}
Pass interface to class you are testing through constructor for example
public class ViewModel
{
private readonly IDisplay _display;
public ViewModel(IDisplay display)
{
_display = display;
}
public void DoSomething()
{
// do something
_display.ShowMessage("result of do something");
}
}
Then in test you will pass implementation for the test
public class FakeDisplay : IDisplay
{
public string LastDisplayedMessage => _lastDisplayedMessage;
public void ShowMessage(string message)
{
_lastDisplayedMessage = message;
}
}
[Test]
public void WhenDoSomething_ShouldShowMessage()
{
var fakeDisplay = new FakeDisplay();
var viewmodel = new ViewModel(fakeDisplay);
viewmodel.DoSomething();
fakeDisplay.LastDisplayedMessage.Should().Be("result of do something");
}
In actual production code you will implement interface which will display message and pass it to the viewmodel.
public class Display : IDisplay
{
public void ShowMessage(string message)
{
MessageBox.Show(message);
}
}
I am abstracting away NLog. So far, what I have...
public interface IAppLogger
{
void Info(string message);
void Warn(string message);
void Error(string message, Exception error);
void Fatal(string message);
....// other overload
}
And an Implementation of IAppLogger using NLog
public class NLogLogger : IAppLogger
{
private readonly NLog.Logger _logger;
public NLogLogger([CallerFilePath] string callerFilePath = "")
{
_logger = NLog.LogManager.GetLogger(callerFilePath);
}
public void Info(string message)
{
_logger.Info(message);
}
public void Warn(string message)
{
_logger.Warn(message);
}
.....// and others
}
And Console Application that uses this service
public class Program
{
private static IAppLogger Log { get; set; }
private static void Main()
{
var kernel = new StandardKernel();
kernel.Load(Assembly.GetExecutingAssembly());
Log = kernel.Get<IAppLogger>();
Log.Info("Application Started");
Log.Warn("Developer: Invalid date format");
Log.Error("Divid by zero error", new DivideByZeroException());
Console.WriteLine("\nDone Logging");
Console.ReadLine();
}
}
And a dependency Injection using Ninject
public class NinjectConfig : NinjectModule
{
public override void Load()
{
Bind<IAppLogger>().To<NLogLogger>()
.WithConstructorArgument("callerFilePath", GetParentTypeName);
}
private static string GetParentTypeName(IContext context)
{
return context.Request.ParentRequest.Service.FullName;
}
}
so far so good. But When I run the application, Ninject keeps returning NULL for context.Request.ParentRequest. I also tried it with context.Request.Target........ Still it returns NULL for context.Request.Target. What am I doing wrong. Help me out please!!!!
Found an answer here that may work for you, give this a try:
public class NLogLogger : IAppLogger
{
private readonly NLog.Logger _logger;
public NLogLogger(Type callerType)
{
_logger = NLog.LogManager.GetLogger(callerType.Name,callerType);
}
public void Info(string message)
{
_logger.Info(message);
}
public void Warn(string message)
{
_logger.Warn(message);
}
.....// and others
}
public class NinjectConfig : NinjectModule
{
public override void Load()
{
Bind<IAppLogger>().ToMethod(p => new NLogLogger(p.Request.Target.Member.DeclaringType));
}
}
I would not be surprised if this has been answered somewhere, the problem is I am not sure how to phrase a search to find what I need. The things I have already found have either been too simplistic to be usable or poorly explained such that I cannot translate it into my own project. I had no formal instruction with event handlers, delegates, and the like (heck, I didn't even learn about Entity-Component Systems--or other design patterns--until long after I graduated college and was already employed as a programmer, and even then it wasn't something I learned at, or for, my job).
Essentially what I want to know is, what does the definition of Array.Sort<T>(T[] array, Comparison<T> comparison) look like?
There's clearly some kind of generalization going on, as myCompareDelegate(...) takes two arguments of any type. In almost everything I've found relating to Func arguments, a Func<> parameter requires explicitly declared types, with the exception of some sample code using an operator I am unfamiliar with:
SomeUtility(arg => new MyType());
public void SomeUtility<T>(Func<object, T> converter) {
var myType = converter("foo");
}
It compiles but I have no idea what it does and as such, I do not know how to utilize it to create code that will run or do what I want to do.
My goal here is to be able to create an event system (yes, I'm aware that C# has an event system built in, but again, all the sample code I've seen is either simplified to the point of uselessness--listeners contained in the same class as the dispatcher--or complicated and unexplained). I want the following to be true:
a single function to register an event listener (for any Type of event and its subtypes)
a single function to dispatch an event (calling only the relevant listeners)
to be able to create new event types without having to modify the functions for registration and handling (no explicit types in the dispatcher beyond the base event class) provided the new event type extends the allowable event type (i.e. an Entity will only dispatch EntityEvents not WorldEvents).
I have a system that works currently, but it requires that all my handlers pass through a single "onEvent" function which takes a base event object and figures out what it's actual type is, passing that off to the true handler.
Eg:
//Entity implements IEventDispatcher
public SomeConstructor(Entity ent) {
//public delegate void EventListener(EventBase eventData); is declared
//in the IEventDispatcher interface.
ent.attachEvent(typeof(EntityEventPreRender), new EventListener(onEvent));
ent.attachEvent(typeof(EntityEventPostRender), new EventListener(onEvent));
}
//EntityEventPreRender extends EntityEventRender extends EntityEvent extends EventBase
//EntityEventPostRender extends EntityEventRender extends EntityEvent extends EventBase
public void onEvent(EventBase data) {
if(data is EntityEventPreRender)
onPre((EntityEventPreRender)data);
if(data is EntityEventPostRender)
onPost((EntityEventPostRender)data);
}
public void onPre(EntityEventPreRender evt) {}
public void onPost(EntityEventPostRender evt) {}
attachEvent() here is a function that takes a Type (used as a HashMap key) and a Delegate and stores it in a list (the HashMap value). Dispatching the event just needs to pass the EventData object, which is queried for its type (via evt.GetType()) to retrieve the list of listeners, then invoking them: listItem(evt)
But I'd rather be able to just do this:
public SomeConstructor(Entity ent) {
ent.attachEvent(onPre);
ent.attachEvent(onPost);
}
public void onPre(EntityEventPreRender evt) {}
public void onPost(EntityEventPostRender evt) {}
But I cannot, for the life of me, figure out how to do this because I do not know how to declare the attachEvent() function to take a generic function parameter the way Array.Sort<T>(T[] array, Comparison<T> comparison) does. I get the error:
"The type arguments for method doSomething<T>(SomeClass.Thing<T>)' cannot be inferred from the usage. Try specifying the type arguments explicitly."
I think you might be looking for something like the following:
public static class PubSub<TMessage>
{
private static List
<
Action
<
TMessage
>
> listeners = new List<Action<TMessage>>();
public static void Listen(Action<TMessage> listener)
{
if (listener != null) listeners.Add(listener);
}
public static void Unlisten(Action<TMessage> listener)
{
if (listeners.Contains(listener)) listeners.Remove(listener);
}
public static void Broadcast(TMessage message)
{
foreach(var listener in listeners) listener(message);
}
}
In the above code, using PubSub and specifying a type for TMessage creates a new static class in memory with its own memory space allocated for storing a separate list of listeners. The compiler will ensure that only the substituted type for TMessage and its subclasses will be allowed in that list, provided you consistently use the base type as the type argument for the TMessage type parameter.
You would then use it like so:
public class SomeMessageType
{
public int SomeId;
public string SomeDescription;
}
public class SomePublisher
{
public void DoSomethingCool(string description)
{
var randomizer = new Random();
...
PubSub<SomeMessageType>.Broadcast(new SomeMessageType(){SomeId = randomizer.Next(), SomeDescription = description});
}
}
public class SomeListener
{
static SomeListener()
{
PubSub<SomeMessageType>.Listen(SomeMessageEvent);
}
private static void SomeMessageEvent(SomeMessageType message)
{
// do something with the message
}
}
If you then create another class SomeOtherMessageType which does not inherit from SomeMessageType and make similar calls to it, it will only broadcast to listeners of that specific type.
EDITED:
Here is a full proof of concept that compiles that you can run in a console app to allay any remaining concerns you may have over efficacy of this technique.
using System;
using System.Collections.Generic;
namespace TestPubSub
{
public class Program
{
public static void Main(string[] args)
{
Program.startListeners();
Program.sendTestMessages();
Program.stopConsoleFromExitingImmediately();
}
private static void startListeners()
{
SomeListener.Listen();
SomeOtherListener1.Listen();
SomeOtherListener2.Listen();
}
private static void sendTestMessages()
{
var publisher1 = new SomePublisher();
var publisher2 = new SomeOtherPublisher();
publisher1.DoSomethingCool("Hello world");
publisher2.DoSomethingElse(DateTime.Now);
}
private static void stopConsoleFromExitingImmediately()
{
Console.ReadKey();
}
}
public static class PubSub<TMessage>
{
private static List
<
Action
<
TMessage
>
> listeners = new List<Action<TMessage>>();
public static void Listen(Action<TMessage> listener)
{
if (listener != null) listeners.Add(listener);
}
public static void Unlisten(Action<TMessage> listener)
{
if (listeners.Contains(listener)) listeners.Remove(listener);
}
public static void Broadcast(TMessage message)
{
foreach(var listener in listeners) listener(message);
}
}
public class SomeMessageType
{
public int SomeId;
public string SomeDescription;
}
public class SomeOtherMessageType
{
public DateTime SomeDate;
public Double SomeAmount;
}
public class SomePublisher
{
public void DoSomethingCool(string description)
{
var randomizer = new Random();
PubSub<SomeMessageType>.Broadcast(new SomeMessageType(){SomeId = randomizer.Next(), SomeDescription = description});
}
}
public class SomeOtherPublisher
{
public void DoSomethingElse(DateTime when)
{
var randomizer = new Random();
PubSub<SomeOtherMessageType>.Broadcast(new SomeOtherMessageType(){SomeAmount = randomizer.NextDouble(), SomeDate = when});
}
}
public class SomeListener
{
public static void Listen()
{
PubSub<SomeMessageType>.Listen(SomeMessageEvent);
}
private static void SomeMessageEvent(SomeMessageType message)
{
Console.WriteLine("Attention! SomeMessageType receieved by SomeListener with\r\nid: {0}\r\ndescription: {1}\r\n", message.SomeId, message.SomeDescription);
}
}
public class SomeOtherListener1
{
public static void Listen()
{
PubSub<SomeOtherMessageType>.Listen(SomeMessageEvent);
}
private static void SomeMessageEvent(SomeOtherMessageType message)
{
Console.WriteLine("Heads up! SomeOtherMessageType receieved by SomeOtherListener1 with\r\namount: {0}\r\ndate: {1}\r\n", message.SomeAmount, message.SomeDate);
}
}
public class SomeOtherListener2
{
public static void Listen()
{
PubSub<SomeOtherMessageType>.Listen(SomeMessageEvent);
}
private static void SomeMessageEvent(SomeOtherMessageType message)
{
Console.WriteLine("Yo! SomeOtherMessageType receieved by SomeOtherListener2 withr\namount: {0}\r\ndate: {1}\r\n", message.SomeAmount, message.SomeDate);
}
}
}
EDITED AGAIN (Alternate proof of concept using an instance based pubs):
Here is a proof of concept using an instance based PubSub.
using System;
using System.Collections.Generic;
namespace TestPubSub
{
public class Program
{
private static PubSub<SomeMessageType> pubSub1 = new PubSub<SomeMessageType>();
private static PubSub<SomeOtherMessageType> pubSub2 = new PubSub<SomeOtherMessageType>();
private static SomeListener listener1 = new SomeListener();
private static SomeOtherListener1 listener2 = new SomeOtherListener1();
private static SomeOtherListener2 listener3 = new SomeOtherListener2();
public static void Main(string[] args)
{
Program.startListeners();
Program.sendTestMessages();
Program.stopConsoleFromExitingImmediately();
}
private static void startListeners()
{
Program.listener1.Listen(Program.pubSub1);
Program.listener2.Listen(Program.pubSub2);
Program.listener3.Listen(Program.pubSub2);
}
private static void sendTestMessages()
{
var publisher1 = new SomePublisher(Program.pubSub1);
var publisher2 = new SomeOtherPublisher(Program.pubSub2);
publisher1.DoSomethingCool("Hello world");
publisher2.DoSomethingElse(DateTime.Now);
}
private static void stopConsoleFromExitingImmediately()
{
Console.ReadKey();
}
}
public class PubSub<TMessage>
{
private List
<
Action
<
TMessage
>
> listeners = new List<Action<TMessage>>();
public void Listen(Action<TMessage> listener)
{
if (listener != null) this.listeners.Add(listener);
}
public void Unlisten(Action<TMessage> listener)
{
if (listeners.Contains(listener)) this.listeners.Remove(listener);
}
public void Broadcast(TMessage message)
{
foreach(var listener in this.listeners) listener(message);
}
}
public class SomeMessageType
{
public int SomeId;
public string SomeDescription;
}
public class SomeOtherMessageType
{
public DateTime SomeDate;
public Double SomeAmount;
}
public class SomePublisher
{
private PubSub<SomeMessageType> pubSub;
public SomePublisher(PubSub<SomeMessageType> pubSub) { this.pubSub = pubSub; }
public void DoSomethingCool(string description)
{
var randomizer = new Random();
this.pubSub.Broadcast(new SomeMessageType(){SomeId = randomizer.Next(), SomeDescription = description});
}
}
public class SomeOtherPublisher
{
private PubSub<SomeOtherMessageType> pubSub;
public SomeOtherPublisher(PubSub<SomeOtherMessageType> pubSub) { this.pubSub = pubSub; }
public void DoSomethingElse(DateTime when)
{
var randomizer = new Random();
this.pubSub.Broadcast(new SomeOtherMessageType(){SomeAmount = randomizer.NextDouble(), SomeDate = when});
}
}
public class SomeListener
{
public void Listen(PubSub<SomeMessageType> pubSub)
{
pubSub.Listen(this.SomeMessageEvent);
}
private void SomeMessageEvent(SomeMessageType message)
{
Console.WriteLine("Attention! SomeMessageType receieved by SomeListener with\r\nid: {0}\r\ndescription: {1}\r\n", message.SomeId, message.SomeDescription);
}
}
public class SomeOtherListener1
{
public void Listen(PubSub<SomeOtherMessageType> pubSub)
{
pubSub.Listen(this.SomeMessageEvent);
}
private void SomeMessageEvent(SomeOtherMessageType message)
{
Console.WriteLine("Heads up! SomeOtherMessageType receieved by SomeOtherListener1 with\r\namount: {0}\r\ndate: {1}\r\n", message.SomeAmount, message.SomeDate);
}
}
public class SomeOtherListener2
{
public void Listen(PubSub<SomeOtherMessageType> pubSub)
{
pubSub.Listen(this.SomeMessageEvent);
}
private void SomeMessageEvent(SomeOtherMessageType message)
{
Console.WriteLine("Yo! SomeOtherMessageType receieved by SomeOtherListener2 withr\namount: {0}\r\ndate: {1}\r\n", message.SomeAmount, message.SomeDate);
}
}
}