{"id":22126538,"url":"https://github.com/amarok79/amarok.events","last_synced_at":"2025-12-25T11:38:55.442Z","repository":{"id":63513302,"uuid":"137562439","full_name":"Amarok79/Amarok.Events","owner":"Amarok79","description":"A fast and light-weight implementation of the observer pattern that supports synchronous and asynchronous invocation and/or subscribers. A potential replacement for regular .NET events.","archived":false,"fork":false,"pushed_at":"2024-11-16T12:04:36.000Z","size":2492,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-16T12:30:54.715Z","etag":null,"topics":["async","dotnet","events","observer-pattern"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Amarok79.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-16T07:30:36.000Z","updated_at":"2024-11-16T12:04:40.000Z","dependencies_parsed_at":"2024-11-16T12:26:06.254Z","dependency_job_id":"9847f4ca-9236-4dbc-9636-87ce6fa4dcf8","html_url":"https://github.com/Amarok79/Amarok.Events","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Amarok79%2FAmarok.Events","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Amarok79%2FAmarok.Events/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Amarok79%2FAmarok.Events/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Amarok79%2FAmarok.Events/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Amarok79","download_url":"https://codeload.github.com/Amarok79/Amarok.Events/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227598457,"owners_count":17791605,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","dotnet","events","observer-pattern"],"created_at":"2024-12-01T16:58:50.691Z","updated_at":"2025-12-25T11:38:55.428Z","avatar_url":"https://github.com/Amarok79.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿[![NuGet](https://img.shields.io/nuget/v/Amarok.Events.svg?logo=)](https://www.nuget.org/packages/Amarok.Events/)\n\n# Introduction\n\nThis library provides a fast and light-weight implementation of the observer pattern, which can be used as a replacement\nfor regular .NET events. The implementation supports raising events in a *synchronous, blocking* or *asynchronous,\nawait-able* fashion. Besides, both *synchronous* and *asynchronous* event handler can be registered.\n\nThe implementation is a bit slower than ordinary .NET events regarding raw call performance, but optimized to avoid\nallocations and therefore suitable for high-performance scenarios or resource-constraint embedded systems.\n\n# Redistribution\n\nThe library is redistributed as NuGet package: [Amarok.Events](https://www.nuget.org/packages/Amarok.Events/)\n\nThe package provides strong-named binaries for *.NET Standard 2.0*, *.NET 6.0*, *.NET 8.0*, and *.NET 10.0*. Tests are performed with *.NET Framework 4.8*, *.NET 6.0*, *.NET 8.0*, and *.NET 10.0*.\n\n# Documentation\n\nTable of Content:\n\n- [Introduction](#introduction)\n- [Redistribution](#redistribution)\n- [Documentation](#documentation)\n    - [Event Source and Event](#event-source-and-event)\n    - [Invoke with Synchronous Event Handler](#invoke-with-synchronous-event-handler)\n    - [Invoke with Asynchronous Event Handler](#invoke-with-asynchronous-event-handler)\n    - [InvokeAsync with Asynchronous Event Handler](#invokeasync-with-asynchronous-event-handler)\n    - [InvokeAsync with Synchronous Event Handler](#invokeasync-with-synchronous-event-handler)\n    - [Raising Events](#raising-events)\n    - [Weak Subscriptions](#weak-subscriptions)\n    - [Exception Behavior](#exception-behavior)\n    - [IProgress\\\u003cT\\\u003e Integration](#iprogresst-integration)\n    - [Event Recorder](#event-recorder)\n- [Frequently Asked Questions](#frequently-asked-questions)\n    - [Is this library thread-safe?](#is-this-library-thread-safe)\n    - [What are the advantages compared to .NET events?](#what-are-the-advantages-compared-to-net-events)\n    - [What about Reactive Extensions?](#what-about-reactive-extensions)\n    - [Who uses this library?](#who-uses-this-library)\n\n### Event Source and Event\n\nSuppose you have an interface and you want to expose an event on that interface. You do that as follows:\n\n```cs\npublic interface IFooService\n{\n    Event\u003cInt32\u003e Progress { get; }\n}\n```\n\nThe event is declared as *getter-only property* of type **Event\\\u003cT\u003e**, where **T** represents the type of event\nargument. **T** can be of any type.\n\nThe implementation class of that interface then initializes a field of type **EventSource\\\u003cT\u003e** and implements the\ngetter-only event property.\n\n```cs\ninternal sealed class FooServiceImpl : IFooService\n{\n    private readonly EventSource\u003cInt32\u003e mProgressEventSource = new EventSource\u003cInt32\u003e();\n\n    public Event\u003cInt32\u003e Progress =\u003e mProgressEventSource.Event;\n\n    public void DoSomething()\n    {\n        // raises the event\n        mProgressEventSource.Invoke(50);\n    }\n}\n```\n\nIn general, the *event source* should be kept private, while the associated **Event\\\u003cT\u003e** is made public. This is\nsimilar to the pattern used for *CancellationToken* and *CancellationTokenSource*, or *Task\\\u003cT\u003e* and\n*TaskCompletionSource\\\u003cT\u003e*.\n\nFor raising the event, one calls **Invoke(**..**)** on the *event source*. Here you supply the event argument that is\nforwarded to all event handlers.\n\nNext, a consumer of the service can subscribe to the event. It just has to call **Subscribe(**..**)** on the *event*\nthat is made public by the service.\n\n```cs\nFooServiceImpl serviceImpl = new FooServiceImpl();\nIFooService service = serviceImpl;\n\nIDisposable subscription = service.Progress.Subscribe(x =\u003e {\n    Console.WriteLine(x + \"%\");\n});\n\nserviceImpl.DoSomething();        // internally raises the event\n// console output:    50%\n```\n\nThe object returned from **Subscribe(**..**)** can be used to cancel the subscription at any time.\n\n```cs\nsubscription.Dispose();\n\nserviceImpl.DoSomething();\n// does nothing, since no subscribers are registered anymore\n```\n\nIt is recommended that subscribers store these subscription objects somewhere. Otherwise, they won't be able to remove\ntheir registered event handlers.\n\nIf instead the class exposing the event wants to cancel all subscriptions, for example, because it gets disposed, it can\ndispose the *event source* too, which automatically cancels all subscriptions and ignores further calls to **Invoke(**..\n**)**.\n\n```cs\ninternal sealed class FooServiceImpl : IFooService\n{\n    ...\n\n    public void Dispose()\n    {\n        mProgressEventSource.Dispose();\n        // cancels all subscriptions, discards new subscriptions and\n        // ignores any call to Invoke()\n    }\n}\n```\n\n### Invoke with Synchronous Event Handler\n\nThe following code snippet shows a single *event source* with two event handlers. Both event handler and also the code\ninvoking the event print to the console. What`s the console output?\n\n```cs\nvar source = new EventSource\u003cString\u003e();\n\nsource.Event.Subscribe(x =\u003e {        // sync event handler\n    Console.WriteLine(x + \"1\");\n});\n\nsource.Event.Subscribe(x =\u003e {        // sync event handler\n    Console.WriteLine(x + \"2\");\n});\n\nConsole.WriteLine(\"A\");\nsource.Invoke(\"B\");\nConsole.WriteLine(\"C\");\n```\n\nThe output is:\n\n    A\n    B1\n    B2\n    C\n\nThis shows that event handlers are invoked directly by the thread that calls **Invoke()**. There is no additional\nthreading introduced by the library. Also, **Invoke()** returns directly after all event handlers have completed.\n\nPlease note, the order in which event handlers are invoked is not deterministic. You shouldn't rely on that.\n\n### Invoke with Asynchronous Event Handler\n\nNow, let's take the same example but slightly modified with *async* event handlers. What's the output of this?\n\n```cs\nvar source = new EventSource\u003cString\u003e();\n\nsource.Event.Subscribe(async x =\u003e {        // async event handler\n    await Task.Delay(100);\n    Console.WriteLine(x + \"1\");\n});\n\nsource.Event.Subscribe(async x =\u003e {        // async event handler\n    await Task.Delay(200);\n    Console.WriteLine(x + \"2\");\n});\n\nConsole.WriteLine(\"A\");\nsource.Invoke(\"B\");\nConsole.WriteLine(\"C\");\n```\n\nThe output is:\n\n    A\n    C\n    ...\n    B1    (100 ms delayed)\n    B2    (200 ms delayed)\n\nAgain, the thread calling **Invoke()** is also calling the event handlers. However, this time, it returns after\nencountering the first *await* statement, causing **Invoke()** to return earlier as the event handler's continuations.\n\nThat means a consumer can decide whether it wants to register a synchronous or asynchronous event handler. In the latter\ncase, from a perspective of the event raiser, the behavior is kind of fire-and-forget, because the event raiser can't be\nsure that all event handlers have completed when **Invoke()** returned.\n\nIf you need that guarantee then use **InvokeAsync()** instead.\n\n### InvokeAsync with Asynchronous Event Handler\n\nAs mentioned previously, **InvokeAsync()** can be used if awaiting the completion of all event handlers is necessary.\n\n```cs\nvar source = new EventSource\u003cString\u003e();\n\nsource.Event.Subscribe(async x =\u003e {        // async event handler\n    await Task.Delay(100);\n    Console.WriteLine(x + \"1\");\n});\n\nsource.Event.Subscribe(async x =\u003e {        // async event handler\n    await Task.Delay(200);\n    Console.WriteLine(x + \"2\");\n});\n\nConsole.WriteLine(\"A\");\nawait source.InvokeAsync(\"B\");            // await !!\nConsole.WriteLine(\"C\");\n```\n\nThis time the output is:\n\n    A\n    ...\n    B1    (100 ms delayed)\n    B2    (200 ms delayed)\n    C\n\nFeels sequential, although it runs fully asynchronous due to the magic of *async* and *await*.\n\nPlease note that there is still no additional threading involved. The thread calling **InvokeAsync()** still starts to\nexecute the event handlers. The only special thing here is that **InvokeAsync()** awaits the completion of all those\nevent handlers.\n\nIf for example, all registered event handlers are async methods but don't await anything, then the entire event\ninvocation would be processed in a synchronous fashion. In fact, the library implementation has special optimizations in\nplace for this specific scenario of async handlers that don't await and complete immediately.\n\n### InvokeAsync with Synchronous Event Handler\n\nOf course, it is also possible to use **InvokeAsync()** for raising events, but with subscribers that register only\nsynchronous event handlers. This is valid, and the library implementation optimizes this scenario so that there is\nlittle overhead even though *async/await* is involved.\n\n```cs\nvar source = new EventSource\u003cString\u003e();\n\nsource.Event.Subscribe(x =\u003e {        // sync event handler\n    Console.WriteLine(x + \"1\");\n});\n\nsource.Event.Subscribe(x =\u003e {        // sync event handler\n    Console.WriteLine(x + \"2\");\n});\n\nConsole.WriteLine(\"A\");\nawait source.InvokeAsync(\"B\");        // await !!\nConsole.WriteLine(\"C\");\n```\n\nOf course, the output is:\n\n    A\n    B1\n    B2\n    C\n\n### Raising Events\n\nWe have already learned that events are raised by calling **Invoke()** or **InvokeAsync()** providing the event\nargument. In most cases, the event argument won't be a simple string or integer value but some class that must be\nconstructed and filled with information.\n\n```cs\nvar source = new EventSource\u003cFooEventArg\u003e();\n\nvar arg = new FooEventArg() { ... };\nsource.Invoke(arg);\n```\n\nNow, what happens if you raise an event and not a single event handler has been registered? In that case, the\nconstruction of a new event argument is wasted CPU instructions and memory allocation, because **Invoke()** returns\nimmediately without doing anything with the supplied event argument.\n\nWhat if you want to avoid such wasteful instructions?\n\nWell, you can use one of the provided overloads that accept a *value factory* for constructing the event argument.\n\n```cs\nsource.Invoke(() =\u003e {\n    return new FooEventArg() { ... }\n    // value factory is only called, if at least a single\n    // event handler is registered\n});\n```\n\nIf you need to pass some value to the *value factory*, you can do that too. Use it to avoid closure allocations.\n\n```cs\nsource.Invoke((arg1) =\u003e {\n    return new FooEventArg() { ... }\n    // value factory is only called, if at least a single\n    // event handler is registered\n},\n123);    // supplied as arg1\n```\n\nThe same overloads are available for **InvokeAsync()** too.\n\n### Weak Subscriptions\n\nIt is possible to register an event handler (synchronous or asynchronous) via weak subscription. That is one that is\nautomatically removed after the event handler has been garbage collected.\n\nWeak subscriptions support a particular but common use case. Using weak subscriptions in a wrong way can be dangerous as\nit can cause hard-to-reproduce bugs. Consider this as a warning. However, correctly used they can help in preventing\nmemory leaks, for example, just because you forgot to manually remove an event handler.\n\nLet's imagine an application where you have a set of services. These services are mainly persistent and live for the\nentire application lifetime. Such services are commonly registered as singletons in a dependency injection container.\n\nSuppose **IUserManagement** represents such a service. As in all the previous examples, the service exposes an event.\n\n```cs\npublic interface IUserManagement\n{\n    Event\u003cUser\u003e UserAdded { get; }\n}\n```\n\nNext, imagine we have consumers of that service that register on that event.\n\nFor example, we might have other persistent services, but they are not a big deal, because they get constructed at some\ntime, register on our **UserAdded** event and the event subscription exists for the remaining application lifetime, same\nas the involved services.\n\nQuite different are user interface related objects like views. Those don't live for the entire application lifetime, but\nget constructed, register on events, get closed, disposed. When you forget to remove a subscription taken by such a view\nyou have a memory leak. The size of leaked memory increases as the view gets opened and closed multiple times.\nThis happens because as in any other observer pattern implementation the observer (in our case the event source)\nmaintains a strong reference to the observable (event handler in our case). This causes quite often memory leaks.\n\nHere come weak subscriptions into play as they can help prevent such memory leaks. They free the developer from the\nburden to manually remove event subscriptions.\n\nSuch a UI view can use weak subscriptions on our **UserAdded** event as follows.\n\n```cs\npublic sealed class BarView\n{\n    // this field is necessary to hold the event subscription\n    private IDisposable mUserAddedSubscription;\n\n    public BarView(IFooService service)\n    {\n        // when using SubscribeWeak() the returned object must be\n        // stored into a field, otherwise the subscription will get\n        // out of scope and get garbage collected too early\n        mUserAddedSubscription = service.UserAdded.SubscribeWeak(\n            x =\u003e HandleUserAdded(x)\n        );\n    }\n\n    private void HandleUserAdded(User user)\n    {\n        ...\n    }\n}\n```\n\nThat's it.\n\nIn fact, there is just one important point here: Store the subscription object returned by **SubscribeWeak()** into a\nmember field of the same object as your event handler.\n\nYou can use that returned object also to cancel the subscription at any time, for example, when the view gets closed.\nThat makes subscription cancellation more deterministic.\n\nIf you don't cancel the subscription manually, it is automatically removed from the service event after the view gets\nclosed and garbage collected. This works since only the view hold a strong reference to the subscription (as shown in\nthe previous example). With weak subscriptions, the *event source* of our service doesn't maintain a strong reference to\nthe subscription and the event handler anymore.\n\nSince the view is kept in memory from other root objects (the UI framework) the subscription is kept alive, and the\nevent handler in the view is invoked as expected. After the view gets closed, all strong references to the view are\nremoved, meaning the view and also it's (the only) strong reference to the subscription are garbage collected.\n\nWeak subscriptions are no silver bullet. As always choose the right tool for the job.\n\n### Exception Behavior\n\nConsider the following example. What if one of the event handlers throws an exception.\n\n```cs\nvar source = new EventSource\u003cString\u003e();\n\nsource.Event.Subscribe(x =\u003e {        // sync event handler\n    Console.WriteLine(x + \"1\");\n    throw new Exception();\n});\n\nsource.Event.Subscribe(x =\u003e {        // sync event handler\n    Console.WriteLine(x + \"2\");\n});\n\nConsole.WriteLine(\"A\");\nsource.Invoke(\"B\");\nConsole.WriteLine(\"C\");\n```\n\nWhat would you expect to happen? Is the second event handler called regardless of the exception? Is the exception\npropagated back to the caller?\n\nWell, regarding exception handling, this library takes a different, maybe controversial approach. I consider it a design\nflaw of regular .NET events that invocation of the remaining event handlers is aborted if one of the previously invoked\nevent handlers threw an exception. Since the thrown exception is reported back to the event publisher, this makes the\nevent publisher dependent on its subscribers, but the entire observer design pattern exists to decouple both, publisher\nand subscribers.\n\nIn my opinion, the pattern only makes sense, if a publisher doesn't need to care about whether there are subscribers, or\nwhether those subscribers fail with exceptions. The publisher's only responsibility is to invoke all registered event\nhandlers in all cases.\n\nSo, our example generates following output:\n\n    A\n    B1\n    B2\n    C\n\nB2 is reliably invoked, even though B1 threw an exception.\n\nBut, what happened with the exception?\n\nWell, the event publisher is NOT bothered with exception handling. Events are there to notify other parts of the\napplication. This is kind of one-way communication. Exceptions are therefore not propagated back to the caller.\n\nInstead, the library catches all exceptions thrown in event handlers and forwards them to the global event *\n*UnobservedException** on **EventSystem**. That way an application can observe all otherwise unobserved exceptions\nthrown in event handlers and at least log them.\n\n```cs\nEventSystem.UnobservedException.Subscribe(ex =\u003e {\n    Console.WriteLine(ex);\n});\n```\n\nIf an application wants to handle those exceptions - and that is generally recommended - then the exception handling\nshould happen in the event handlers itself. If exceptions can occur in an event handler, then the event handler is\nresponsible for careful handling.\n\n### IProgress\\\u003cT\u003e Integration\n\nThe interface **IProgress\\\u003cT\u003e** defined by .NET BCL is a commonly-used way to transfer progress.\n\nFor example, long-running methods often accept an **IProgress\\\u003cT\u003e** as an argument to report back progress.\n\n```cs\npublic void SomeLongRunningMethod(IProgress\u003cInt32\u003e progress)\n{\n    // reports progress back to caller\n    for(Int32 i = 0; i \u003c 100; i++)\n        progress.Report(i);\n}\n```\n\nThe caller then supplies an implementation to receive the progress. This is often done using the ready-made *\n*Progress\\\u003cT\u003e** class.\n\n```cs\nvar progress = new Progress\u003cT\u003e(x =\u003e\n    Console.WriteLine(x)\n);\n\nSomeLongRunningMethod(progress);\n\n// output might be out-of-order\n0\n1\n2\n6\n3\n4\n...\n99\n98\n```\n\nDepending on from which thread you call this long-running method you might be surprised that the console output is not\nalways sequentially increasing numbers from 0 to 99. That's because **Progress\\\u003cT\u003e** invokes its callback via a\nsynchronization context.\n\nThat can be the UI thread, then sequential numbers 0 to 99 will be the result because everything is synchronized via the\nUI thread.\n\nIf it's not the UI thread, then the callback will be invoked via the thread-pool thread, which means callbacks can come\nout-of-order!\n\nAs an alternative, you can supply an **EventSource\\\u003cT\u003e** as progress object. It forwards progress to its subscribers in\nthe correct order.\n\n```cs\nvar progress = new EventSource\u003cT\u003e();\n\nprogress.Event.Subscribe(x =\u003e\n    Console.WriteLine(x)\n);\n\nSomeLongRunningMethod(progress);\n\n// always generates:\n0\n1\n2\n...\n97\n98\n99\n```\n\nOf course, it is also possible to subscribe an **IProgress\\\u003cT\u003e** onto an event, so that raised event arguments are\nforwarded to the progress object.\n\n```cs\nIProgress\u003cInt32\u003e progress = new Progress\u003cInt32\u003e(x =\u003e\n    Console.WriteLine(x)\n);\n\nvar source = new EventSource\u003cInt32\u003e();\nsource.Event.Subscribe(progress);\n\nsource.Invoke(123);\n\n// output:\n123\n```\n\n### Event Recorder\n\nIf you are writing unit tests, then there will come the time where you want to ensure that an event on your\nsubject-under-test is correctly raised with the expected event arguments.\n\nTo ease unit testing, this library provides a ready-made event recorder that can be used to record events and then,\nlater on, analyze them.\n\nAs an example, suppose we have the following implementation class.\n\n```cs\npublic sealed class UserManagementService\n{\n    private readonly EventSource\u003cString\u003e mUserAddedEvent = new EventSource\u003cString\u003e();\n\n    public void AddUser(String name)\n    {\n        mUserAddedEvent.Invoke(name);\n    }\n}\n```\n\nIn a unit test, we want to assert that the event is raised and that the supplied name is supplied to the event.\n\n```cs\n[Test]\npublic void AddUserRaisesEventWithUserName\n{\n    // arrange\n    var sut = new UserManagementService();\n    var recorder = EventRecorder.From(sut.UserAdded);\n\n    // act\n    sut.AddUser(\"Foo\");\n    Thread.Sleep(50);\n    sut.AddUser(\"Bar\");\n\n    // assert (using NFluent assertions)\n    Check.That(recorder.Events)\n        .HasSize(2);        // we expect two events\n\n    Check.That(recorder.Events[0])\n        .IsEqualTo(\"Foo\");    // first \"Foo\" was recorded\n    Check.That(recorder.Events[1])\n        .IsEqualTo(\"Bar\");    // then \"Bar\" as expected\n}\n```\n\nThe event recorder can be used to gain even more information. Instead of accessing property **Events**, one can use *\n*EventInfos**, which returns timing and thread information about the recorded events.\n\n```cs\n    recorder.EventInfos[0].Value        // \"Foo\"\n    recorder.EventInfos[0].Index        // 0\n    recorder.EventInfos[0].Timestamp    // DateTimeOffset\n    recorder.EventInfos[0].TimeOffset    // 0 ms\n    recorder.EventInfos[0].Thread        // the calling thread\n\n    recorder.EventInfos[1].Value        // \"Bar\"\n    recorder.EventInfos[1].Index        // 1\n    recorder.EventInfos[1].Timestamp    // DateTimeOffset\n    recorder.EventInfos[1].TimeOffset    // 50 ms\n    recorder.EventInfos[1].Thread        // the calling thread\n```\n\nIf you don't want to record events temporarily, you can **Pause()** and finally **Resume()** the event recorder. If you\nwant to turn off recording completely, call **Dispose()**. To clear the list of recorded events, you can use **Reset()\n**.\n\n# Frequently Asked Questions\n\n### Is this library thread-safe?\n\nYes, this library is fully thread-safe.\n\n### What are the advantages compared to .NET events?\n\n.NET events are nice. It's great to have a runtime that natively supports events. However, there are also some\nreal-world \"problems\" that this library tries to solve.\n\n- .NET events don't support *async* event handler. You can have *async void* handlers that are invoked with\n  fire-and-forget semantic, but you can't natively await the completion of an async event handler.\n- .NET events don't invoke all remaining event handlers when a previously invoked event handler threw an exception.\n- Removal of event handlers is sometimes a bit hard, especially when you use lambda expressions a lot.\n- .NET events don't support weak subscriptions that are automatically removed after the event handler got\n  garbage-collected.\n\n### What about Reactive Extensions?\n\n[Rx.NET](http://reactivex.io/) is a great technology, but it's API can be a bit difficult to use with it's *OnNext()*,\n*OnCompleted()* and *OnError()* methods. Its strength lies in the processing and coordination of streams of events, not\nin simplicity.\n\n### Who uses this library?\n\nA few years ago, at my day job, we started development of a new software platform for a next-generation product family.\nWe considered using Rx.NET as a replacement for all .NET events because we weren't happy with some limitations of .NET\nevents. However, Rx.NET also doesn't fit well with our requirements.\n\nSo, I started to experiment with a simple observer-pattern implementation on my own that would fulfill our requirements.\nI did that in my free time, but soon the library development was also partly done at my day job, which finally resulted\nin a closed-source solution.\n\nFrom that time on, our closed-source event library was a cornerstone of our new software platform.\n\nNow, again a few years later, I started to rewrite the entire library once again from scratch with the goal to make it\nopen source. I wanted to share it with the community and I also wanted to be able to use it for my own side projects.\n\nThat said, *Amarok.Events* isn't that widely used, but the concepts already have proven to work well. The implementation\nprovided here is already used is several proprietary projects.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famarok79%2Famarok.events","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famarok79%2Famarok.events","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famarok79%2Famarok.events/lists"}