diff --git a/content/add-and-remove-keywords-in-csharp.md b/content/add-and-remove-keywords-in-csharp.md index 680d470..67f373b 100644 --- a/content/add-and-remove-keywords-in-csharp.md +++ b/content/add-and-remove-keywords-in-csharp.md @@ -1,7 +1,7 @@ +++ title = "The add and remove keywords in C#, event handlers" date = 2024-08-16 -updated = 2024-08-16 +updated = 2024-08-24 description = "" [taxonomies] tags = ["C#",".NET","programming"] @@ -33,7 +33,7 @@ namespace EventHandlers { } ``` -As you can see, we have a ’Player’ class, where we keep track of our points. The player can gain points (for instance, by collecting coins) and in the case they have at least 100 points (wow), they earn a shiny achivement. +As you can see, we have a ’Player’ class, where we keep track of our points. The player can gain points (for instance, by collecting coins) and in the case they have at least 100 points (wow), they earn a shiny achievement. In our program we can instantiate the player and then add scores to it. @@ -53,9 +53,9 @@ public class Launcher } ``` -I created this small example program as a Task-Based Async Patter (TAP), because that is the preffered way to add delays and realistically in a game these events should be nonblocking (other things must happen, like updating an enemy position). +I created this small example program as a Task-Based Async Patter (TAP), because that is the preferred way to add delays and realistically in a game these events should be nonblocking (other things must happen, like updating an enemy position). -Let's say I want to make a change in what happens when the achivement level is reached. In that case we would have to modify what happens in the `Player`'s source code. When we think about event and asynchronous execution or programming, this would be an anti-pattern. For instance many UI libraries would need to offer to rebuild the library which provides the functionality for this! This is very incovenient. And in the case of UI libraries or notification based systems, the caller should define what happens when an (event) completes, instead of the called object. +Let's say I want to make a change in what happens when the achievement level is reached. In that case we would have to modify what happens in the `Player`'s source code. When we think about event and asynchronous execution or programming, this would be an anti-pattern. For instance many UI libraries would need to offer to rebuild the library which provides the functionality for this! This is very inconvenient. And in the case of UI libraries or notification based systems, the caller should define what happens when an (event) completes, instead of the called object. One way to solve this problem is to introduce an event action in the `Player` class: @@ -143,6 +143,49 @@ namespace EventHandlers { ```c# namespace EventHandlers; +public class Launcher +{ + private static void OnAchievementUnlocked(uint points) // This is our event handler. + { + Console.WriteLine($"Congratulations! Achievement unlocked at {points} points!"); + } + private static async Task Main(string[] args) + { + var player = new Player(); + + player.AchievementUnlocked += OnAchievementUnlocked; + + await player.AddPoints(50); + await player.AddPoints(40); + await player.AddPoints(40); + } +} +``` + +The great thing about event handlers, is that you can freely define and add more functionality to it. Let's say we have NPC traders. These traders get really impressed about the player after unlocking this specific achievement and want to trade with them. + +You would probably implement this functionality in it's own class. Let's say we have the class `Trader`, where the trader NPC will want to sell the player something in function to how much points they have when unlocking the achievement: + +```c# +// Trader.cs +namespace EventHandlers; + +public class Trader +{ + public void TryTrade(int points) + { + System.Console.WriteLine($"Trader offers {points * 2} wood to the player."); + } +} +``` + +We can add the callback as well: + +```c# +//Launcher.cs +... +namespace EventHandlers; + public class Launcher { private static void OnAchievementUnlocked(uint points) @@ -152,14 +195,198 @@ public class Launcher private static async Task Main(string[] args) { var player = new Player(); + var trader = new Trader(); // Create an NPC trader. player.AchievementUnlocked += OnAchievementUnlocked; + player.AchievementUnlocked += trader.TryTrade; // Call the trader's function. await player.AddPoints(50); await player.AddPoints(40); await player.AddPoints(40); } } +... +``` + +In general we can see a pattern here. One object is defining and raising an event (publisher), while an other object listens to this raised event and reacts to it (subscriber). + +It is very important to understand, that one should unsubscribe from the events they have subscribed to, because the garbage collector will not clean up objects if references are held to it. If the publisher object has a long lifetime and such a reference is held to it in the subscriber, then the subscription is such a reference. + +This causes memory leaks, or rather unfreed memory to crop up over the lifetime of an application. + +[Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor) is solves this by having component classes implement `IDisposable` and unsubscribing there: + +```c# +@page "/example" + +@implements IDisposable + +

Example Component

+ +@code { + private SomeService _someService; + + protected override void OnInitialized() + { + _someService = new SomeService(); + _someService.SomeEvent += OnSomeEvent; + } + + private void OnSomeEvent(object sender, EventArgs e) + { + // Handle the event + } + + public void Dispose() + { + // Unsubscribe from the event when the component is disposed + if (_someService != null) + { + _someService.SomeEvent -= OnSomeEvent; + } + } +} +``` + +You can follow this example in your own code to clean up event handlers, as it is a fundamental part of .NET. + +Of course there are situations, where it is not needed to unsubscribe manually. If the event handler is static (or as a special case of static, a global one), you might not need to do so, because the number of references are fixed and the lifetime is known. Also, even if the number of subscriptions are well defined, if you know for a fact the specific publisher instance is shorter lived than the subscriber, the garbage collector can clear up the associated memory, as when the subscriber goes out of scope, the event handler will no longer point to an in-scope publisher. + +Design patterns can help you achieve this. Using the **Dispose pattern** or **IDisposable pattern** help you in correctly implementing resource cleanup for your classes. This is very useful, because as C# is a managed language and the CLR does provide a really good way to forget about managing resources manually, beyond a certain abstraction, it must be cared for, like unmanaged resources (file handlers, database connections) and event handlers. (Python is actually more advanced in this regard, because via the `with:` block, you can make sure resources are cleaned up and no exception handling is needed.) +Implementing these patterns usually refers to the same thing, they just accentuate different aspects of the same idea. + +.NET also provides a great addition so we do not have to manually define the delegate function. + +You could use `EventHandler` type in the event definition as the type, where `T` is the event data: + +```c# +//EventHandlers.cs +using System; +using System.Threading.Tasks; + +namespace EventHandlers { + internal class Player { + private uint Points { get; set; } + + // Define the event using EventHandler + public event EventHandler? AchievementUnlocked; + + public async Task AddPoints(uint points) + { + Points += points; + Console.WriteLine($"Player earned {points} points!"); + await Task.Delay(new Random().Next(500, 1500)); + if (Points >= 100) + { + // Raise the event, passing the points as event data + AchievementUnlocked?.Invoke(this, Points); + } + } + } +} +``` + +If we had multiple parameters in our delegate function, you could create a custom class which encapsulates all the event data and has a constructor, which can be accessed in the given context and assign that custom type to `T`. + +Also, you do not need to define any delegate type, when using `EventHandler`, because the default delegate function signature for it is `object? sender, EventArgs e` with a `void` return type. + +It should be noted that when you define your own delegate with no arguments and make an event with that type, it will no longer match `object? sender, EventArgs e`, but rather it will have a no parameter type. + +So in the most general sense, every event must have a delegate type, but .NET provides multiple predefined classes with good patterns. I would recommend using `EventHandler` when possible, because you certainly should care about the sender and the event arguments. In most cases events also are only side-effect producing functions and you do not want to return from them. + +`Action` is a more free-form way of defining the event type, because `object? sender, EventArgs e` is not required, but this also means that you kind of break the standard expectation. I would only use this if there is a significant case of speed-up and only internally if possible. + +**IMPORTANT!** +When you define your own delegate, you can also define a return type. But adding this to an action is an anti-pattern, not only because it goes against standard .NET conventions (and many libraries use them, especially `EventHandler` for event-driven programming), but also because in my opinion it breaks the single responsibility from SOLID, because event handlers are not for data retrieval on the type level. The subscriber might write their own retrieval of the data, provided there is a getter for it or is available in the scope. + +Now after all of this, we can understand finally what `add` and `remove` are. + +When we type the addition and removal operators (`+=` and `-=`) to add or remove event handlers, we actually call the compiler generated `add` and `remove` methods. + +Similar to C++ operator overloading, you might want to define how it is done. For instance, you could have a debug build, where it is logged or you might want to optimize performance, by calculating subscription metrics and a lot more. + +For simplicity's sake, I will show a new example. Let's say we have this very simple class with the default `EventHandler`: + +```c# +public class MyClass +{ + // Declare an event using the default add and remove behavior + public event EventHandler MyEvent; + + public void RaiseEvent() + { + MyEvent?.Invoke(this, EventArgs.Empty); + } +} +``` + +Somewhere else it is called like this: + +```c# +var obj = new MyClass(); + +// Subscribe to the event +obj.MyEvent += EventHandlerMethod; + +// Unsubscribe from the event +obj.MyEvent -= EventHandlerMethod; + +void EventHandlerMethod(object sender, EventArgs e) +{ + Console.WriteLine("Event triggered!"); +} +``` + +This code translates mostly into: + +```c# +public class MyClass +{ + // Underlying delegate field + private EventHandler? _myEvent; + + // Custom add and remove accessors (generated by the compiler) + public event EventHandler MyEvent + { + add + { + // Adds a handler to the event + _myEvent += value; + } + remove + { + // Removes a handler from the event + _myEvent -= value; + } + } + + public void RaiseEvent() + { + // Invoke the event handlers + _myEvent?.Invoke(this, EventArgs.Empty); + } +} +``` + +This would still hold true for any combination or actual implementation of `EventHandler`, `Action` or your own delegate. + +Of course, when we use `Action` or `EventHandler`, we do not need to define the delegate field when using `add` or `remove`: + +As an example of a real-world use case I use [Microsoft's documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/add): + +```c# +class Events : IDrawingObject +{ + event EventHandler PreDrawEvent; + + event EventHandler IDrawingObject.OnDraw + { + add => PreDrawEvent += value; + remove => PreDrawEvent -= value; + } +} ``` -**I will continue writing this post via an update, because it is a bit longer then excepted at first!** +Here, the we have a class `Events`, which implements the `IDrawingObject` interface. In the `IDrawingObject` there is method defined `OnDraw`. This class very well demonstrates how one can use custom event accessors `add` and `remove` to redirect the event to our own defined event handler, which is `PreDrawEvent`. This is actually a common pattern for handling events defined in interfaces. + +So all in all, we see that even simple keywords might need a lot of background knowledge to comprehend. Events are an essential part of programming, especially UI related programming, which C# is heavily used for. I hope I could help you all to better understand these concepts. diff --git a/content/entity-framework-01.md b/content/entity-framework-01.md new file mode 100644 index 0000000..a04ff04 --- /dev/null +++ b/content/entity-framework-01.md @@ -0,0 +1,55 @@ ++++ +title = "Primer in databases: monolithic or distributed?" +date = 2024-08-09 +updated = 2024-08-12 +description = "" +[taxonomies] +tags = ["database","SQL","DBMS", ".NET"] ++++ + +One of the most fundamental pieces of computing is data. Most of tend to think about the way we compute things: the algorithms, the hardware, the networks. But it only works as fast or well as the the data structures behind it. + +In practice we use programs which can efficiently store and retrieve on demand our data as we need it. +Or to be more precise.... + +## The CAP trilemma + +To be accurate nowadays databases are not singular, monolithic, even if the end user (an API consumer, a Facebook user, a programmer) might feel like it is. + +Traditionally, we run 1 database instance. This is great because it is simple (well, simpler) to configure and run it. + +To understand why monolithic or centralized databases are actually a good choice, we must first understand what distributed database systems try to solve. Later on when using databases from C# using the framework components provided by .NET, we will see that most of the implementation details can be left to Entity Framework, but we must have a solid understanding of these ideas. + +There are 3 main reasons to consider when using a distributed database: + +It is important to note that only 2 of these 3 can be provided at one time fully. + +1. Consistency: For each read we read the latest data written into the database. +2. Availability: For each request we get a response, wheter it be a result of successful request or an error. +3. Partition Tolerance: The database works as expected even if some nodes are down or unreachable due to network issues. + +These are called together the CAP-trilemma (or some other similar name). +One way to describe a particular type of databse is by mentioning which 2 are fullfilled: CA/CP/AP. + +Examples of these groups are: + +- AP: Apache Cassandra, CouchDB, DynamoDB. +- CP: Redis, MongoDB, Apache Hbase +- CA: MySQL, Microsoft SQL Server, PostreSQL, Amazon Redshift + +Most commonly databases are CA-type. + +However, not all cases require anything other than CA. Let's see when I think it is a great idea to use single-instance databases: + +- Local application states: The settings of an application, game states, etc. Mostly done via SQLite. As this data is not critical and only accessed locally speed is the upmost importance here. If there is any inconsistency it can be easily fixed, but due to the simple nature of it there is no real danger of inconsistencies. Maybe a race condition or a critical system error can trigger inconsistent writes, but that is only from 1 source anyway. Performance is extremely important for more complex databse system, for instance in games game assets need to be loaded on the fly in a smart way. +- Cache storage: On a server - for instance on a Nextcloud server - [Redis can be used](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/caching_configuration.html#id2) as a method to keep track of locked files. As Redis is an in-memory database (well, to be honest Redis can be looked as a key-value storage type as well). I would like to note here that with Redis Clusters you can actually turn Redis from a CA to CP type database. +- Small servers: When working with servers, one should consider size as a determining factor to decide. If it is known that there will be no high load on the server or data is rarely accessed, there is no need to add partitioning to it. +- Development: During active development, it is practical to set up a small local and reproducible deployment of the database. Unless you want to test partitioned instances, it is a lot easier to set up a single partition. Also, for unit tests it is needed in many cases, however it is worth to note that many testing frameworks provide options to add different backends to run the unit tests to decouple possible issues from the database. + +I would like to point out that not adding partitioning to the a database is not equivivalent to not making backups. I personally follow the 3-2-1 rule of backups. There should at the very least be 3 copies of the data: 1 instance of the production data, a backup of the production data on a different physical copy and one off-site for disaster recovery. I personally think that the data should be version controlled as well. For me, this means using an incremental backup copy strategy, like Kopia (which is cross-platform and I use for my Nextcloud server and my Linux desktop installtion) and MacOS's included Time Machine. + +## Addendum + +Today I explained a way of categorizing databases. The reason why I did not start writing about SQL/noSQL, document databases, graph databases is that technically any type of database can be a monolithic or partitioned one and for any and all the CAP-trilemma stands. + +Int the next post in this series, I will go into detail in that way, as it will be crucial to understand before writing Entity Framework code.