{"id":15779902,"url":"https://github.com/aldaviva/koko","last_synced_at":"2025-03-14T08:32:35.369Z","repository":{"id":65497006,"uuid":"133358540","full_name":"Aldaviva/KoKo","owner":"Aldaviva","description":"🐵 Declarative automatic state management in .NET with no boilerplate. Never deal with INotifyPropertyChanged again.","archived":false,"fork":false,"pushed_at":"2024-09-26T12:26:29.000Z","size":183,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-06T04:53:04.131Z","etag":null,"topics":["knockout","models","observable","properties","reactive","reactive-programming","state-management"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/KoKo/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Aldaviva.png","metadata":{"files":{"readme":"ReadMe.md","changelog":null,"contributing":null,"funding":null,"license":"License.txt","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},"funding":{"custom":["https://paypal.me/aldaviva"]}},"created_at":"2018-05-14T12:38:17.000Z","updated_at":"2024-11-25T07:55:58.000Z","dependencies_parsed_at":"2024-12-08T16:33:20.164Z","dependency_job_id":null,"html_url":"https://github.com/Aldaviva/KoKo","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aldaviva%2FKoKo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aldaviva%2FKoKo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aldaviva%2FKoKo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aldaviva%2FKoKo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Aldaviva","download_url":"https://codeload.github.com/Aldaviva/KoKo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243547616,"owners_count":20308741,"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":["knockout","models","observable","properties","reactive","reactive-programming","state-management"],"created_at":"2024-10-04T18:21:51.908Z","updated_at":"2025-03-14T08:32:35.068Z","avatar_url":"https://github.com/Aldaviva.png","language":"C#","funding_links":["https://paypal.me/aldaviva"],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://raw.githubusercontent.com/Aldaviva/KoKo/master/KoKo/icon.png\" height=\"24\" alt=\"KoKo logo\" /\u003e KoKo\n===\n\n[![Nuget](https://img.shields.io/nuget/v/KoKo?logo=nuget)](https://www.nuget.org/packages/KoKo/) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Aldaviva/KoKo/dotnetpackage.yml?branch=master\u0026logo=github) [![Coveralls](https://img.shields.io/coveralls/github/Aldaviva/KoKo?logo=coveralls)](https://coveralls.io/github/Aldaviva/KoKo?branch=master)\n\n*Knockout for Cocoa, for C#*\n\nKoKo lets you create `Property` objects as members of your model classes.\n\nUnlike [native C# properties](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties), KoKo `Property` objects automatically fire change [events](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/). They can be composed from multiple other Properties without the dependencies being aware of the dependents, and without writing any boilerplate event handling code. They are compatible with native C# properties and events, as well as with WPF and Windows Forms databinding.\n\nThese properties are very similar to what you would find in [Knockout](https://knockoutjs.com/), [MobX](https://mobx.js.org/), and WPF's [`DependencyProperty`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.dependencyproperty). They do not rely on a presentation layer like WPF, and they do not require you to import and understand a large, overblown, confusing library like [.NET Reactive Extensions/Rx.NET](https://dotnetfoundation.org/projects/reactive-extensions).\n\nThis library was ported from an open-source Swift library by [@abrindam](https://github.com/abrindam) called KoKo (which means \"Knockout for Cocoa\"), which was later renamed to [Yoyo](https://github.com/plluke/Yoyo) because \"KoKo\" and \"Cocoa\" are homophones and thus verbally indistinguishable.\n\n\u003cp\u003e\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003eTable of Contents\u003c/strong\u003e\u003c/summary\u003e\n\n\u003c!-- MarkdownTOC autolink=\"true\" bracket=\"round\" autoanchor=\"true\" levels=\"1,2,3\" --\u003e\n\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Usage](#usage)\n    - [Example](#example)\n- [Types of Properties](#types-of-properties)\n    - [**`StoredProperty`**](#storedproperty)\n    - [**`DerivedProperty`**](#derivedproperty)\n    - [`ConnectableProperty`](#connectableproperty)\n    - [`ManuallyRecalculatedProperty`](#manuallyrecalculatedproperty)\n    - [`MultiLevelProperty`](#multilevelproperty)\n    - [`NativeReadableProperty`](#nativereadableproperty)\n    - [`NativeWritableProperty`](#nativewritableproperty)\n    - [`PassthroughProperty`](#passthroughproperty)\n    - [`TentativeProperty`](#tentativeproperty)\n- [Events on Properties](#events-on-properties)\n- [Threading](#threading)\n\n\u003c!-- /MarkdownTOC --\u003e\n\u003c/details\u003e\u003c/p\u003e\n\n\u003ca id=\"requirements\"\u003e\u003c/a\u003e\n## Requirements\n- Any of the following runtimes\n    - .NET Core 2.0 or later, including .NET 5 or later\n    - .NET Framework 4.6.2 or later\n    - Any other runtime that supports [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard) or later\n\n\u003ca id=\"installation\"\u003e\u003c/a\u003e\n## Installation\n**[KoKo on NuGet Gallery](https://www.nuget.org/packages/KoKo/)**\n\n1. Right-click on your C# project and go to Manage NuGet Packages.\n1. Under Browse, search for `KoKo`.\n1. Click the down arrow button for `KoKo`.\n\n```ps1\n# Alternately, using .NET CLI\ndotnet add package KoKo\n\n# Alternately, using Package Manager PowerShell\nInstall-Package KoKo\n```\n\n\u003ca id=\"usage\"\u003e\u003c/a\u003e\n## Usage\n1. Create a `class` to act as your business or view model.\n1. Import the `KoKo.Property` namespace.\n1. Add some `Property` fields, one for each piece of data you want to represent.\n1. Get and set their `Value`.\n\n\u003ca id=\"example\"\u003e\u003c/a\u003e\n### Example\nThis is a just a silly, simple example. [Don't actually represent people's names this way.](https://www.w3.org/International/questions/qa-personal-names)\n\n```cs\nusing KoKo.Property;\n\nnamespace MyProject {\n\n    public class Person {\n\n        private StoredProperty\u003cstring\u003e FirstName { get; }\n        private StoredProperty\u003cstring\u003e LastName { get; }\n        public Property\u003cstring\u003e FullName { get; }\n\n        public Person(string firstName, string lastName) {\n            FirstName = new StoredProperty\u003cstring\u003e(firstName);\n            LastName = new StoredProperty\u003cstring\u003e(lastName);\n            FullName = DerivedProperty\u003cstring\u003e.Create(FirstName, LastName, (first, last) =\u003e $\"{first} {last}\");\n        }\n\n        public void SetFirstName(string firstName) {\n            FirstName.Value = firstName;\n        }\n\n    }\n}\n```\n\n\u003ca id=\"programmatic-access\"\u003e\u003c/a\u003e\n#### Programmatic access\nNow you can get a `person` object's autogenerated full name,\n\n```cs\nvar person = new Person(\"Alice\", \"Smith\");\nConsole.WriteLine(person.FullName.Value); // Alice Smith\n```\n\nand if you change a dependency value, the dependent full name will be automatically updated.\n\n```cs\nperson.SetFirstName(\"Bob\");\nConsole.WriteLine(person.FullName.Value); // Bob Smith\n```\n\n\u003ca id=\"data-bound-access\"\u003e\u003c/a\u003e\n#### Data-bound access\n\nYou can also use Properties with databinding:\n\n```xaml\n\u003c!-- WPF --\u003e\n\u003cLabel Content=\"{Binding FullName.Value}\" /\u003e\n```\n\n```cs\n// Windows Forms\nprivate void Form1_Load(object sender, System.EventArgs e) {\n    personNameLabel.DataBindings.Add(\"Text\", model.FullName, \"Value\");\n}\n```\n\nRemember to use the `Value` property in your databinding declarations, otherwise you won't get the property's value.\n\n\u003ca id=\"types-of-properties\"\u003e\u003c/a\u003e\n## Types of Properties\n\n\u003ca id=\"storedproperty\"\u003e\u003c/a\u003e\n### **`StoredProperty`**\n\n- Stores a single value in memory\n- Value can be get or set imperatively\n- Similar to a native C# property, except you don't have to [implement `INotifyPropertyChanged` yourself](https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interface)\n- You can perform [atomic operations](https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked#methods) on a `StoredProperty` value using `Increment()`, `Decrement()`, `Add(value)`, `Exchange(value)`, or `CompareExchange(possibleNewValue, assignIfOldValueEquals)`\n\n```cs\nvar a = new StoredProperty\u003cstring\u003e(\"world\");\nConsole.WriteLine($\"Hello {a.Value}\"); // Hello world\na.Value = \"world!\";\nConsole.WriteLine($\"Hello {a.Value}\"); // Hello world!\n```\n\n\u003ca id=\"derivedproperty\"\u003e\u003c/a\u003e\n### **`DerivedProperty`**\n\n- Automatically calculates its value from one or more other properties (_dependencies_), which can be any type of KoKo property\n- You don't need to manually invoke event handlers on the dependencies when their values change, this property will automatically recalculate its value instead\n- You cannot set its value directly, but you can get the value just like a [`StoredProperty`](#storedProperty)\n- Unlike other KoKo property classes, you construct `DerivedProperty` instances using the `DerivedProperty.Create()` factory method instead of a constructor. This allows you to pass a type-safe calculator function that doesn't capture the dependency properties.\n\n```cs\nvar b = new StoredProperty\u003cint\u003e(8);\nDerivedProperty\u003cint\u003e absoluteValueB = DerivedProperty\u003cint\u003e.Create(b, (bValue) =\u003e System.Math.Abs(bValue));\nConsole.WriteLine($\"The absolute value of {b.Value} is {absoluteValueB.Value}.\"); // The absolute value of 8 is 8.\nb.Value = -9;\nConsole.WriteLine($\"The absolute value of {b.Value} is {absoluteValueB.Value}.\"); // The absolute value of -9 is 9.\n```\n\n\u003ca id=\"connectableproperty\"\u003e\u003c/a\u003e\n### `ConnectableProperty`\n- Like a [`PassthroughProperty`](#passthroughProperty), except the dependent `ConnectableProperty` has its dependency `Property` passed to it instead of depending upon it at creation time\n- You can also pass a constant value instead of a `Property`, which is more convenient that constructing a whole new `StoredProperty` instance to hold that constant\n- Useful for inversion of control where the dependent must not be aware of how to obtain its dependencies\n- Can be reconnected and disconnected multiple times, to allow reconfiguration\n\n```cs\nvar connectable = new ConnectableProperty\u003cint\u003e(0);\nvar a = new StoredProperty\u003cint\u003e(8);\nvar b = 9;\nConsole.WriteLine(connectable.Value); // 0\nconnectable.Connect(a);\nConsole.WriteLine(connectable.Value); // 8\nconnectable.Connect(b);\nConsole.WriteLine(connectable.Value); // 9\n```\n\n\u003ca id=\"manuallyrecalculatedproperty\"\u003e\u003c/a\u003e\n### `ManuallyRecalculatedProperty`\n- Like a [`DerivedProperty`](#derivedProperty), except instead of recalculating its value based on changes to dependency properties, you must manually instruct the property to recalculate its value\n- Useful when the dependencies do not have a way to expose change events\n- Useful when the dependencies are constantly changing and you don't care about every change\n- Useful when the recalculation is very expensive and you want to reduce how often it is run\n- Alternatively, if you want to reuse the same calculator, expose a parameterless constructor, or parameterize multiple instances, you can subclass `ManuallyRecalculatedProperty\u003cT\u003e`, call the parameterless super-constructor, and override the `ComputeValue()` method with your calculator logic, rather than passing a calculator function to the super-constructor.\n\n```cs\nvar manuallyRecalculated = new ManuallyRecalculatedProperty\u003clong\u003e(() =\u003e DateTimeOffset.Now.ToUnixTimeMilliseconds());\nConsole.WriteLine(manuallyRecalculated); // 1591651725420\nThread.Sleep(1000);\nmanuallyRecalculated.Recalculate();\nConsole.WriteLine(manuallyRecalculated); // 1591651726420\n```\n\n\u003ca id=\"multilevelproperty\"\u003e\u003c/a\u003e\n### `MultiLevelProperty`\n- Like a [`PassthroughProperty`](#passthroughProperty), except it gets a property value nested at an arbitrary depth\n- Useful if you have an object graph with properties whose values are classes with other properties\n- Useful if the top-level reference may change to a different instance (which person is currently logged in), but the property chain you want to refer to is always the same (the full name of the currently logged-in person)\n\n```cs\nvar person = new Person(\"FirstName\", \"LastName\");\nvar currentUser = new StoredProperty\u003cPerson\u003e(person);\nvar currentUserFullName = new MultiLevelProperty\u003cstring\u003e(() =\u003e currentUser.Value.fullName);\nConsole.WriteLine($\"Welcome, {currentUserFullName.Value}\"); // Welcome, FirstName LastName\n```\n\n\u003ca id=\"nativereadableproperty\"\u003e\u003c/a\u003e\n### `NativeReadableProperty`\n- Useful for interoperation with C# classes, whether they expose property value changes using [`INotifyPropertyChanged`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged) or other events\n\n```cs\n// MyNativePropertyClass implements INotifyPropertyChanged and fires PropertyChanged events\nvar nativePropertyObject = new MyNativePropertyClass { NativeProperty = 8 };\nvar kokoProperty = new NativeReadableProperty\u003cint\u003e(nativePropertyObject, nameof(NativePropertyClass.NativeProperty));\nConsole.WriteLine(kokoProperty.Value); // 8\n```\n\n- If the class does not implement `INotifyPropertyChanged`, this class assumes that it should listen for `*Changed` events that are named after the native property, so that it can tell when the native property value changes. For example, given the `Text` property on the `ToolStripStatusLabel` class, `NativeReadableProperty` will listen for `TextChanged` events.\n\n```cs\n// ToolStripStatusLabel does not implement INotifyPropertyChanged, and instead fires TextChanged events\nvar toolStripStatusLabel = new ToolStripStatusLabel { Text = \"ready\" };\n// The event name TextChanged is inferred from the Text property name\nvar kokoProperty = new NativeReadableProperty\u003cstring\u003e(toolStripStatusLabel, nameof(toolStripStatusLabel.Text));\n```\n\n- If the event name does not follow the `propertyName + \"Changed\"` naming convention, you may override it with the optional third `nativeEventName` parameter.\n\n```cs\n// MyNativePropertyClass2 does not implement INotifyPropertyChanged, and instead fires custom NativePropertyChanged events\nvar nativePropertyObject = new MyNativePropertyClass2 { NativeProperty = 8 };\nvar kokoProperty = new NativeReadableProperty\u003cint\u003e(nativePropertyObject, nameof(NativePropertyClass2.NativeProperty),\n    nameof(NativePropertyClass2.NativePropertyChanged));\nConsole.WriteLine(kokoProperty.Value); // 8\n```\n\n\u003ca id=\"nativewritableproperty\"\u003e\u003c/a\u003e\n### `NativeWritableProperty`\n- Like a [`NativeReadableProperty`](#nativeReadableProperty), except you can also change the value\n- Useful when the native C# property has an accessible setter\n\n```cs\n// MyNativePropertyClass implements INotifyPropertyChanged and fires PropertyChanged events\nvar nativePropertyObject = new MyNativePropertyClass { NativeProperty = 8 };\nvar kokoProperty = new NativeWritableProperty\u003cint\u003e(nativePropertyObject, nameof(NativePropertyClass.NativeProperty));\nConsole.WriteLine(kokoProperty.Value); // 8\nkokoProperty.Value = 9;\nConsole.WriteLine(nativePropertyObject.NativeProperty); // 9\n```\n\n```cs\n// MyNativePropertyClass2 does not implement INotifyPropertyChanged, and instead fires custom NativePropertyChanged events\nvar nativePropertyObject = new MyNativePropertyClass2 { nativeProperty = 8 };\nvar kokoProperty = new NativeWritableProperty\u003cint\u003e(nativePropertyObject, nameof(NativePropertyClass2.NativeProperty)); // Implicit event name NativePropertyChanged\nConsole.WriteLine(kokoProperty.Value); // 8\nkokoProperty.Value = 9;\nConsole.WriteLine(nativePropertyObject.NativeProperty); // 9\n```\n\n```cs\n// MyNativePropertyClass2 does not implement INotifyPropertyChanged, and instead fires custom NativePropertyChanged events\nvar nativePropertyObject = new MyNativePropertyClass2 { nativeProperty = 8 };\nvar kokoProperty = new NativeWritableProperty\u003cint\u003e(nativePropertyObject, nameof(NativePropertyClass2.NativeProperty),\n    nameof(NativePropertyClass2.NativePropertyChanged)); // Explicit event name NativePropertyChanged\nConsole.WriteLine(kokoProperty.Value); // 8\nkokoProperty.Value = 9;\nConsole.WriteLine(nativePropertyObject.NativeProperty); // 9\n```\n\n\u003ca id=\"passthroughproperty\"\u003e\u003c/a\u003e\n### `PassthroughProperty`\n\n- Like a [`DerivedProperty`](#derivedProperty), except it depends on a single property and does not transform the value at all\n- Useful for organizing your class structure and defining boundaries of knowledge or responsibility\n\n```cs\nvar backing = new StoredProperty\u003cdouble\u003e(3.0);\nvar passthrough = new PassthroughProperty\u003cdouble\u003e(a);\nConsole.WriteLine($\"{passthrough.Value} liters\"); // 3 liters\nbacking.Value = 5.0;\nConsole.WriteLine($\"{passthrough.Value} liters\"); // 5 liters\n```\n\n\u003ca id=\"tentativeproperty\"\u003e\u003c/a\u003e\n### `TentativeProperty`\n- Like a [`PassthroughProperty`](#passthroughProperty), except you can supply a temporary overriding value, which it will use for a specified duration before reverting to the passthrough value\n- Useful for dealing with changes which require a long time to take effect, but you want to make it look like it took effect immediately, while still allowing it to be eventually consistent if the change fails.\n- For example, clicks on a button that performs a slow remote operation which changes the button's state, but you want the button's state to update early, before the operation completes, so that the user doesn't think their click was ignored and knows what state the system is trying to reach.\n\n```cs\nvar backing = new StoredProperty\u003cint\u003e(8);\nvar tentative = new TentativeProperty\u003cint\u003e(backing, TimeSpan.FromMilliseconds(500));\nConsole.WriteLine(tentative.Value); // 8\nbacking.Value = 9;\nConsole.WriteLine(tentative.Value); // 9\ntentative.Value = 10;\nbacking.Value = 11;\nConsole.WriteLine(tentative.Value); // 10\nThread.Sleep(1000);\nConsole.WriteLine(tentative.Value); // 11\n```\n\n\u003ca id=\"events-on-properties\"\u003e\u003c/a\u003e\n## Events on Properties\n\nYou can subscribe to events that are fired when any `Property`'s value changes.\n\nIf you want to react to a property changing by changing some other data, you may want to use a `DerivedProperty` or similar, because all value change dependencies will be consistent and probably simpler to understand.\n\nOn the other hand, if you want to react to a property changing by taking some action, then you can listen for the `PropertyChanged` event:\n\n```cs\nvar property = new StoredProperty\u003cint\u003e(1);\nproperty.PropertyChanged += (object sender, KoKoPropertyChangedEventArgs\u003cint\u003e args) =\u003e {\n    Console.WriteLine($\"Property value changed from {args.OldValue} to {args.NewValue}.\");\n};\nproperty.Value = 2; // Property value changed from 1 to 2.\n```\n\nIf you need to pass a KoKo `Property` to a consumer that only accepts `INotifyPropertyChanged`, this interface is also implemented.\n\n```cs\nvar property = new StoredProperty\u003cint\u003e(1);\n((INotifyPropertyChanged property).PropertyChanged += (object sender, PropertyChangedEventArgs args) =\u003e {\n    Console.WriteLine($\"Property value changed to {property.Value}.\");\n};\nproperty.Value = 2; // Property value changed to 2.\n```\n\n\u003ca id=\"threading\"\u003e\u003c/a\u003e\n## Threading\n\nYou may want the property changed event handlers to run on a different thread than the one that caused the property value to change in the first place. This is especially important for updating UI controls, since Windows Forms and WPF only allow UI updates on the main thread, whether the update is imperative or declarative (data binding).\n\nTo accomplish this, set `EventSynchronizationContext` on your KoKo `Property` to [`SynchronizationContext.Current`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext.current).\n\nNow, whenever the Property value changes, even if the change happened on a background thread, the event handlers will run in that [`SynchronizationContext`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext), so if you have a WPF control bound to that Property value, it will run in the correct WPF [`Dispatcher`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcher).\n\n- All KoKo event handlers run synchronously, whether on the original thread or through a `SynchronizationContext`\n- All KoKo Properties can have their `EventSynchronizationContext` changed, not just `PassthroughProperty`\n\n```cs\nvar backing = new StoredProperty\u003cdouble\u003e(3.0);\nvar passthrough = new PassthroughProperty\u003cdouble\u003e(backing);\npassthrough.EventSynchronizationContext = SynchronizationContext.Current;\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faldaviva%2Fkoko","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faldaviva%2Fkoko","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faldaviva%2Fkoko/lists"}