{"id":13798922,"url":"https://github.com/SIDOVSKY/EBind","last_synced_at":"2025-05-13T06:31:48.580Z","repository":{"id":50372875,"uuid":"352151901","full_name":"SIDOVSKY/EBind","owner":"SIDOVSKY","description":"🔵 .NET Data Binding we deserve: concise, fast, feature-rich","archived":false,"fork":false,"pushed_at":"2021-07-18T04:03:52.000Z","size":536,"stargazers_count":163,"open_issues_count":3,"forks_count":7,"subscribers_count":8,"default_branch":"main","last_synced_at":"2024-11-14T11:07:07.809Z","etag":null,"topics":["csharp","data-binding","dotnet","maui","mvvm","uwp","winforms","wpf","xamarin","xamarin-forms"],"latest_commit_sha":null,"homepage":"","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/SIDOVSKY.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}},"created_at":"2021-03-27T18:50:50.000Z","updated_at":"2024-10-08T14:02:15.000Z","dependencies_parsed_at":"2022-08-12T21:11:27.666Z","dependency_job_id":null,"html_url":"https://github.com/SIDOVSKY/EBind","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SIDOVSKY%2FEBind","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SIDOVSKY%2FEBind/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SIDOVSKY%2FEBind/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SIDOVSKY%2FEBind/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SIDOVSKY","download_url":"https://codeload.github.com/SIDOVSKY/EBind/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225183789,"owners_count":17434182,"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":["csharp","data-binding","dotnet","maui","mvvm","uwp","winforms","wpf","xamarin","xamarin-forms"],"created_at":"2024-08-04T00:00:56.717Z","updated_at":"2024-11-18T13:31:40.985Z","avatar_url":"https://github.com/SIDOVSKY.png","language":"C#","readme":"\u003cp align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"Assets/logo.svg\" alt=\"EBind\" height=\"150\"\u003e\u003cbr\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://github.com/SIDOVSKY/EBind/actions/workflows/ci.yml\"\u003e\n    \u003cimg src=\"https://github.com/SIDOVSKY/EBind/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.nuget.org/packages/EBind.NET/\"\u003e\n    \u003cimg src=\"https://img.shields.io/nuget/v/EBind.NET?logo=nuget\" alt=\"nuget: EBind\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.nuget.org/packages/EBind.LinkerIncludeGenerator/\"\u003e\n    \u003cimg src=\"https://img.shields.io/nuget/v/EBind.LinkerIncludeGenerator?label=nuget%20%7C%20EBind.LinkerIncludeGenerator\u0026logo=nuget\" alt=\"nuget: EBind.LinkerIncludeGenerator\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/SIDOVSKY/EBind\"\u003e\n    \u003cimg src=\"https://img.shields.io/codecov/c/gh/sidovsky/ebind?label=coverage%20%28strict%29\u0026logo=codecov\" alt=\"Codecov\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003c!-- snippet: Bind --\u003e\n\u003ca id='snippet-bind'\u003e\u003c/a\u003e\n```cs\nvar binding = new EBinding\n{\n    () =\u003e view.Text == vm.Text,\n    () =\u003e view.Text == vm.Description.Title.Text,\n    () =\u003e view.Text == (vm.Text ?? vm.FallbackText),\n    () =\u003e view.Visible == !vm.TextVisible,\n    () =\u003e view.Visible == (vm.TextVisible == vm.ImageVisible),\n    () =\u003e view.Visible == (vm.TextVisible \u0026\u0026 vm.ImageVisible),\n    () =\u003e view.Visible == (vm.TextVisible || vm.ImageVisible),\n    () =\u003e view.FullName == $\"{vm.FirstName} {vm.LastName}\",\n    () =\u003e view.FullName == vm.FirstName + \" \" + vm.LastName,\n    () =\u003e view.Timestamp == Converter.DateTimeToEpoch(vm.DateTime),\n\n    BindFlag.TwoWay,\n    () =\u003e view.Text == vm.Text,\n    () =\u003e view.SliderValueFloat == vm.AgeInt,\n\n    BindFlag.OneTime | BindFlag.NoInitialTrigger,\n    () =\u003e view.ShowImage(vm.ImageUri),\n    () =\u003e Dispatcher.RunOnUiThread(() =\u003e view.ShowImage(vm.ImageUri)),\n\n    BindFlag.OneTime,\n    (view, nameof(view.Click), vm.OnViewClicked),\n    (view, nameof(view.TextEditedEventHandler), () =\u003e vm.OnViewTextEdited(view.Text)),\n    (view, \"CustomEventForGesture\", vm.OnViewClicked),\n\n    (view, nameof(view.Click), vm.ViewClickCommand),\n    (view, nameof(view.Click), () =\u003e vm.ViewClickCommand.TryExecute(view.Text)),\n\n    new UserExtensions.CustomEBinding(),\n};\n\nbinding.Dispose();\n```\n\u003csup\u003e\u003ca href='/EBind.Tests/Snippets/Sample.cs#L64-L98' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-bind' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n### Three types of bindings here\n\n* \u003ca id='equality'\u003e**EQUALITY**\u003c/a\u003e – bind a property or an expression of them to another property.  \n  Every change of every property on the right (`vm.Text`) leads to an assignment of the property on the left (`view.Text`).  \n  Binding between two properties may work in a Two-Way mode.\n  ```cs\n  () =\u003e view.Text == vm.Text,\n  ```\n* \u003ca id='action'\u003e**ACTION**\u003c/a\u003e – bind a method to its target and parameters.  \n  Every time `vm.ImageUri` is changed `view.ShowImage` is invoked with a new value.\n  ```cs\n  () =\u003e view.ShowImage(vm.ImageUri),\n  ```\n* \u003ca id='event'\u003e**EVENT**\u003c/a\u003e – bind a method or command to an event.  \n  Every time `view.Click` is raised `vm.OnViewClicked` is called.\n  ```cs\n  (view, nameof(View.Click), vm.OnViewClicked),\n  ```\n\n### Key points\n\n- \u003ca name=\"main-update-trigger\"\u003e\u003c/a\u003e\n  **The main binding update trigger is [`INotifyPropertyChanged.PropertyChanged`](https://docs.microsoft.com/dotnet/api/system.componentmodel.inotifypropertychanged.propertychanged)**.  \n  Boilerplate code may be avoided with [Fody.PropertyChanged](https://github.com/Fody/PropertyChanged).  \n  There are some platform-specific [pre-configured triggers](#pre-configured-triggers). Additional ones are easy to [configure](#member-triggers).\n\n- **Bindings invoke immediately after the construction, except for the [event binding](#event).**  \n  To skip initial invocation prepend `BindFlag.NoInitialTrigger`.\n\n- **Each property and field in a binding expression triggers the binding update**  \n  ![Triggers Highlight](Assets/triggers-highlight.svg)\n\n- **Default binding mode is One-Way, Source-to-Target (right to left).**  \n  This is the most common use-case. Explicit declaration of a two-way mode will help to avoid unreasonable performance loss.  \n  May be overridden in `EBinding.DefaultConfiguration.DefaultFlag`.\n\n- **For Target-to-Source binding simply swap operands.**  \n  It seems more natural than bringing in an additional `BindFlag.OneWayToSource`.\n\n- **Expressions of any complexity are supported.**  \n  Even MONSTERS like that:\n  ```cs\n  () =\u003e view.Text == new Func\u003cstring?\u003e(() =\u003e new Dictionary\u003cstring, string?\u003e((int)10.0){ { \"Key\", vm.Text } }[\"Key\"])(),\n  ```\n  However, the more complex the expression, the longer it takes to create and invoke the binding from it. See [benchmarks](#benchmark-ebind-creation) for the reference.\n\n- **`BindFlag` applies to all subsequent bindings till the next flag.**\n\n## Configuration and Extensibility\n\n### Pre-Configured Triggers\n\n\u003cdetails\u003e\u003csummary\u003eXamarin.Android\u003c/summary\u003e\n\n  | View/Control                  | Event           | Property                                           |\n  |-------------------------------|-----------------|----------------------------------------------------|\n  | Android.Views.View            | Click           |                                                    |\n  |                               | LongClick       |                                                    |\n  |                               | FocusChange     |                                                    |\n  | Android.Widget.AdapterView    | ItemSelected    | SelectedItemPosition                               |\n  |                               | ItemClick       |                                                    |\n  |                               | ItemLongClick   |                                                    |\n  | Android.Widget.CalendarView   | DateChange      | Date                                               |\n  | Android.Widget.CompoundButton | CheckedChange   | Checked                                            |\n  | Android.Widget.DatePicker     | DateChanged     | DateTime                                           |\n  | Android.Widget.NumberPicker   | ValueChanged    | Value                                              |\n  | Android.Widget.RatingBar      | RatingBarChange | Rating                                             |\n  | Android.Widget.SearchView     | QueryTextChange | Query                                              |\n  | Android.Widget.SeekBar        | ProgressChanged | Progress                                           |\n  | Android.Widget.TextView       | TextChanged     | Text                                               |\n  | Android.Widget.TimePicker     | TimeChanged     | Hour (API 23+)\u003cbr\u003eMinute (API 23+)\u003cbr\u003eCurrentHour\u003cbr\u003eCurrentMinute |\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eXamarin.iOS\u003c/summary\u003e\n\n  | View/Control       | Event                  | Property        |\n  |--------------------|------------------------|-----------------|\n  | UIBarButtonItem    | Clicked                |                 |\n  | UIControl          | TouchUpInside          |                 |\n  |                    | ValueChanged           |                 |\n  | UIDatePicker       | ValueChanged           | Date            |\n  | UIPageControl      | ValueChanged           | CurrentPage     |\n  | UISearchBar        | TextChanged            | Text            |\n  | UISegmentedControl | ValueChanged           | SelectedSegment |\n  | UISlider           | ValueChanged           | Value           |\n  | UIStepper          | ValueChanged           | Value           |\n  | UISwitch           | ValueChanged           | On              |\n  | UITabBarController | ViewControllerSelected | SelectedIndex   |\n  | UITextField        | EditingChanged         | Text            |\n  |                    | EditingDidBegin        |                 |\n  |                    | EditingDidEnd          |                 |\n  | UITextView         | Changed                | Text            |\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eXamarin.Forms\u003c/summary\u003e\n\n  All views implement `INotifyPropertyChanged` so the [main trigger](#main-update-trigger) is invoked for every bindable property change. \n\u003c/details\u003e\n\n### Member Triggers\n\nIn `Configuration` you may specify how to subscribe and unsubscribe for signals of property and field updates that are not tracked out of the box.  \nThere are overloads for the property/field update handler as `System.EventHandler`, `System.EventHandler\u003cTEventArgs\u003e` or any other class:\n\n\u003c!-- snippet: Configure-Member-Trigger --\u003e\n\u003ca id='snippet-configure-member-trigger'\u003e\u003c/a\u003e\n```cs\nEBinding.DefaultConfiguration.ConfigureTrigger\u003cView, string\u003e(\n    v =\u003e v.Text,\n    (v, h) =\u003e v.TextEditedEventHandler += h,\n    (v, h) =\u003e v.TextEditedEventHandler -= h);\n\nEBinding.DefaultConfiguration.ConfigureTrigger\u003cView, View.TextEditedEventArgs, string\u003e(\n    v =\u003e v.Text,\n    (v, h) =\u003e v.TextEditedGenericEventHandler += h,\n    (v, h) =\u003e v.TextEditedGenericEventHandler -= h);\n\nEBinding.DefaultConfiguration.ConfigureTrigger\u003cView, Action\u003cstring\u003e, string\u003e(\n    v =\u003e v.Text,\n    trigger =\u003e _ =\u003e trigger(),\n    (v, h) =\u003e v.TextEditedCustomEventHandler += h,\n    (v, h) =\u003e v.TextEditedCustomEventHandler -= h);\n```\n\u003csup\u003e\u003ca href='/EBind.Tests/Snippets/Sample.cs#L17-L33' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-configure-member-trigger' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n### Event Triggers\n\nYou may configure your own triggers for [event bindings](#event) under custom identifiers even if they represent a Pub-Sub pattern differently from C# events (`IObservable`, `Add/RemoveListener` methods).\n\n\u003c!-- snippet: Configure-Custom-Event-Trigger --\u003e\n\u003ca id='snippet-configure-custom-event-trigger'\u003e\u003c/a\u003e\n```cs\nEBinding.DefaultConfiguration.ConfigureTrigger\u003cView, View.GestureRecognizer\u003e(\n    \"CustomEventForGesture\",\n    trigger =\u003e new View.GestureRecognizer(trigger),\n    (v, h) =\u003e v.AddGestureRecognizer(h),\n    (v, h) =\u003e v.RemoveGestureRecognizer(h));\n```\n\u003csup\u003e\u003ca href='/EBind.Tests/Snippets/Sample.cs#L53-L59' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-configure-custom-event-trigger' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nThe same identifier (event name) can be used with multiple classes.  \nFor example on Xamarin.iOS a [pre-defined](/EBind/Platform/Configuration.ios.cs#LC56:~:text=void%20ConfigureExtraEvents(),%7D) `Tap` event can be used with both `UIControl` and `UIView`:\n```cs\nusing EBind;\nusing static EBind.Platform.Configuration.ExtraEventNames;\n\nvar binding = new EBinding\n{\n    (uiButton, Tap, OnButtonClick),\n    (uiImageView, Tap, OnImageClick),\n};\n```\n\nConfiguration of C# events is **not required** – they can be found by the name: `nameof(obj.Event)`.  \nBut it's recommended to specify subscription and unsubscription delegates to improve cold-start performance and avoid [linker errors](#linking).\n\n\u003c!-- snippet: Configure-Event-Trigger --\u003e\n\u003ca id='snippet-configure-event-trigger'\u003e\u003c/a\u003e\n```cs\nEBinding.DefaultConfiguration.ConfigureTrigger\u003cView\u003e(\n    nameof(View.TextEditedEventHandler),\n    (v, h) =\u003e v.TextEditedEventHandler += h,\n    (v, h) =\u003e v.TextEditedEventHandler -= h);\n\nEBinding.DefaultConfiguration.ConfigureTrigger\u003cView, View.TextEditedEventArgs\u003e(\n    nameof(View.TextEditedGenericEventHandler),\n    (v, h) =\u003e v.TextEditedGenericEventHandler += h,\n    (v, h) =\u003e v.TextEditedGenericEventHandler -= h);\n\nEBinding.DefaultConfiguration.ConfigureTrigger\u003cView, Action\u003cstring\u003e\u003e(\n    nameof(View.TextEditedCustomEventHandler),\n    trigger =\u003e _ =\u003e trigger(),\n    (v, h) =\u003e v.TextEditedCustomEventHandler += h,\n    (v, h) =\u003e v.TextEditedCustomEventHandler -= h);\n```\n\u003csup\u003e\u003ca href='/EBind.Tests/Snippets/Sample.cs#L35-L51' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-configure-event-trigger' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nEvent triggers configured for a class are available to all its children unless they have their own variation defined.\n\nTriggers may be overwritten by onward setups including the pre-defined ones.\n\n### Binding Dispatcher\n\nSometimes ViewModel properties are set from the background thread that leads to data-binding view update triggering in the non-ui thread.\n\nEstablishing a proper thread switching in place is not possible for some code. Also, the risk of missing a thread is not acceptable or not worth caring about for some projects, and delegation of this problem is a good deal.  \nIn this case, data-binding as a point of integration may be a good place to switch threads between the ui-threaded View and the thread-insensitive ViewModel layers.\n\nDispatchers which force the UI thread are set up in `Configuration`:\n```cs\nEBinding.DefaultConfiguration.AssignmentDispatchDelegate = Dispatcher.RunOnUiThread;\nEBinding.DefaultConfiguration.ActionDispatchDelegate = Dispatcher.RunOnUiThread;\n```\n\nFor Xamarin platform [`Xamarin.Essentials.MainThread.BeginInvokeOnMainThread`](https://docs.microsoft.com/en-us/xamarin/essentials/main-thread) will be the best option most of the time.\n\n### Custom Bindings\n\nEBinding collection initializer accepts any form of `IEBinding`.  \nBy implementing this simple interface you can create your own binding types and adapters for data-binding components of other systems (e.g. [Rx.Net](https://github.com/dotnet/reactive)), use them in the same collection initializer, and keep all bindings in one place.\n\nCustom binding creation can be encapsulated inside an extension method `Add(this EBinding, ...)` so that the binding inputs are accepted in the collection initializer (supported since C# 6).\n\nAfter a custom binding is created, it must be added to the collection via `EBinding.Add(IEBinding)` so that it can be disposed along with other bindings.\n\n\u003c!-- snippet: Custom-EBinding --\u003e\n\u003ca id='snippet-custom-ebinding'\u003e\u003c/a\u003e\n```cs\npublic class CustomEBinding : IEBinding\n{\n    public void Dispose()\n    {\n    }\n}\n\npublic static void Add(this EBinding bindingHolder, CustomEBinding binding,\n    [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)\n{\n    try\n    {\n        if (binding == null)\n            throw new ArgumentNullException(nameof(binding));\n\n        if (bindingHolder.CurrentFlag == BindFlag.OneTime)\n            throw new NotSupportedException();\n\n        bindingHolder.Add(binding);\n    }\n    catch (Exception ex)\n    {\n        throw new Exception(\n            $\"Error in entry {bindingHolder.Count} at line #{sourceLineNumber}\", ex);\n    }\n}\n```\n\u003csup\u003e\u003ca href='/EBind.Tests/Snippets/Sample.cs#L167-L194' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-custom-ebinding' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nIt's recommended to decorate exceptions during binding creation with additional information about the binding position (`EBinding.Count`) and its line number ([`CallerLineNumberAttribute`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callerlinenumberattribute)) as debuggers do not highlight that.\n\n\u003cdetails\u003e\u003csummary\u003eException screenshot\u003c/summary\u003e\n\n  ![Location info in exception](Assets/location_info_in_exception.png)\n\u003c/details\u003e\n\n## Benchmarks\n\n\u003cdetails\u003e\u003csummary\u003eEnvironment\u003c/summary\u003e\n\n``` ini\nBenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1)\nAMD Ryzen 5 1600, 1 CPU, 12 logical and 6 physical cores\n.NET Core SDK=5.0.202\n  [Host]     : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT\n  DefaultJob : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT\n```\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003eComparison: Trigger \u003csup\u003e\u003ca id='benchmark-comparison-trigger' href='#benchmark-comparison-trigger' title='Anchor'\u003e🔗\u003c/a\u003e\u003c/sup\u003e\u003c/summary\u003e\n\n|               Method |          Mean |      Error |     StdDev |    Ratio |  Gen 0 | Allocated |\n|--------------------- |--------------:|-----------:|-----------:|---------:|-------:|----------:|\n|                EBind |      68.71 ns |   0.422 ns |   0.395 ns |     1.00 | 0.0305 |     128 B |\n|                Mugen |     206.29 ns |   2.305 ns |   2.156 ns |     3.00 | 0.0172 |      72 B |\n| XamarinFormsCompiled |     351.32 ns |   1.516 ns |   1.418 ns |     5.11 | 0.0267 |     112 B |\n|            MvvmLight |   1,070.28 ns |   3.529 ns |   3.128 ns |    15.58 | 0.1259 |     528 B |\n|            MvvmCross |   1,368.35 ns |   8.320 ns |   7.376 ns |    19.92 | 0.1678 |     704 B |\n|           ReactiveUI |   3,054.42 ns |  30.750 ns |  28.763 ns |    44.45 | 0.1831 |     777 B |\n|       PraeclarumBind | 150,506.37 ns | 372.197 ns | 329.943 ns | 2,190.78 | 0.9766 |    4415 B |\n\n\u003csup\u003e[sources](EBind.Benchmarks/Comparison_Trigger.cs)\u003c/sup\u003e\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003eComparison: Creation, One-Way \u003csup\u003e\u003ca id='benchmark-comparison-creation-one-way' href='#benchmark-comparison-creation-one-way' title='Anchor'\u003e🔗\u003c/a\u003e\u003c/sup\u003e\u003c/summary\u003e\n\n|               Method |       Mean |     Error |    StdDev | Ratio |  Gen 0 | Allocated |\n|--------------------- |-----------:|----------:|----------:|------:|-------:|----------:|\n| XamarinFormsCompiled |   2.955 us | 0.0117 us | 0.0110 us |  0.74 | 0.1831 |     768 B |\n|                EBind |   3.994 us | 0.0240 us | 0.0213 us |  1.00 | 0.5264 |    2232 B |\n|            MvvmLight |   7.118 us | 0.0506 us | 0.0474 us |  1.78 | 0.5951 |    2504 B |\n|                Mugen |   8.075 us | 0.1554 us | 0.1790 us |  2.01 | 0.4349 |    2014 B |\n|            MvvmCross |   9.263 us | 0.0583 us | 0.0546 us |  2.32 | 0.9155 |    3873 B |\n|           ReactiveUI |  49.217 us | 0.8883 us | 0.8309 us | 12.34 | 3.5400 |   14953 B |\n|       PraeclarumBind | 300.919 us | 0.6047 us | 0.5657 us | 75.32 | 2.4414 |   10634 B |\n\n\u003csup\u003e[sources](EBind.Benchmarks/Comparison_Creation.cs)\u003c/sup\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eComparison: Creation, Two-Way \u003csup\u003e\u003ca id='benchmark-comparison-creation-two-way' href='#benchmark-comparison-creation-two-way' title='Anchor'\u003e🔗\u003c/a\u003e\u003c/sup\u003e\u003c/summary\u003e\n\n|               Method |       Mean |     Error |    StdDev |  Ratio |  Gen 0 | Allocated |\n|--------------------- |-----------:|----------:|----------:|-------:|-------:|----------:|\n| XamarinFormsCompiled |   3.058 us | 0.0167 us | 0.0156 us |   0.62 | 0.1831 |     768 B |\n|                EBind |   4.961 us | 0.0309 us | 0.0289 us |   1.00 | 0.7553 |    3184 B |\n|                Mugen |   7.669 us | 0.0444 us | 0.0370 us |   1.55 | 0.4883 |    2064 B |\n|            MvvmLight |   8.812 us | 0.0465 us | 0.0435 us |   1.78 | 0.7782 |    3288 B |\n|            MvvmCross |  13.041 us | 0.0972 us | 0.0909 us |   2.63 | 1.0529 |    4449 B |\n|           ReactiveUI |  80.447 us | 0.4630 us | 0.4331 us |  16.22 | 6.4697 |   27237 B |\n|       PraeclarumBind | 572.390 us | 3.0060 us | 2.8118 us | 115.39 | 3.9063 |   19551 B |\n\n\u003csup\u003e[sources](EBind.Benchmarks/Comparison_Creation_TwoWay.cs)\u003c/sup\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eComparison: Cold Start \u003csup\u003e\u003ca id='benchmark-comparison-cold-start' href='#benchmark-comparison-cold-start' title='Anchor'\u003e🔗\u003c/a\u003e\u003c/sup\u003e\u003c/summary\u003e\n\n`IterationCount=1  LaunchCount=100  RunStrategy=ColdStart`\n\n|     Type |               Method |         Mean |     Error |      StdDev | Ratio | Allocated |\n|--------- |--------------------- |-------------:|----------:|------------:|------:|----------:|\n| Creation |                EBind |  13,948.6 us | 411.78 us | 1,214.14 us |  1.00 |    3504 B |\n| Creation |            MvvmLight |  14,719.1 us | 365.87 us | 1,078.77 us |  1.06 |    3216 B |\n| Creation | XamarinFormsCompiled |  18,116.8 us |  93.64 us |   276.11 us |  1.30 |     848 B |\n| Creation |            MvvmCross |  19,323.5 us |  46.63 us |   137.50 us |  1.39 |    6144 B |\n| Creation |       PraeclarumBind |  21,130.5 us |  70.75 us |   208.60 us |  1.52 |   12312 B |\n| Creation |                Mugen |  74,633.6 us | 117.36 us |   346.03 us |  5.37 |    7472 B |\n| Creation |           ReactiveUI | 151,959.5 us | 181.95 us |   536.48 us | 10.94 |   16304 B |\n|  Trigger |                EBind |     705.5 us |   3.10 us |     9.15 us |  0.05 |     224 B |\n|  Trigger |            MvvmCross |   1,144.8 us |   9.13 us |    26.91 us |  0.08 |     704 B |\n|  Trigger | XamarinFormsCompiled |   1,307.2 us |   6.60 us |    19.46 us |  0.09 |     152 B |\n|  Trigger |            MvvmLight |   2,010.1 us |  13.96 us |    41.15 us |  0.14 |     608 B |\n|  Trigger |           ReactiveUI |   3,354.7 us |   9.44 us |    27.84 us |  0.24 |     792 B |\n|  Trigger |       PraeclarumBind |   4,118.7 us |  15.92 us |    46.93 us |  0.30 |    5000 B |\n|  Trigger |                Mugen |   4,326.1 us |  18.50 us |    54.54 us |  0.31 |     152 B |\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eEBind Creation \u003csup\u003e\u003ca id='benchmark-ebind-creation' href='#benchmark-ebind-creation' title='Anchor'\u003e🔗\u003c/a\u003e\u003c/sup\u003e\u003c/summary\u003e\n\n|                                       Method |        Mean |     Error |    StdDev |  Gen 0 | Allocated |\n|--------------------------------------------- |------------:|----------:|----------:|-------:|----------:|\n|             `(a, \"nameof(a.Event)\", Method)` |    337.5 ns |   2.31 ns |   1.93 ns | 0.0858 |     360 B |\n|                                 `a.Method()` |  3,256.9 ns |  20.82 ns |  18.46 ns | 0.4349 |    1824 B |\n|                   `a.Prop == b.Prop` // INPC |  4,089.8 ns |  54.06 ns |  50.57 ns | 0.5264 |    2232 B |\n|                  `a.Method(b.Prop.Method())` |  4,406.1 ns |  20.41 ns |  17.05 ns | 0.5493 |    2312 B |\n|                          `a.Prop == !b.Prop` |  4,611.5 ns |  46.32 ns |  43.33 ns | 0.5646 |    2392 B |\n|           `a.Prop == b.Prop` // EventHandler |  4,658.2 ns |  46.37 ns |  43.37 ns | 0.5112 |    2152 B |\n|                           `a.Float == b.Int` |  4,785.8 ns |  45.54 ns |  38.03 ns | 0.5798 |    2448 B |\n|                           `a.Enum == b.Enum` |  5,265.1 ns |  22.89 ns |  21.42 ns | 0.5493 |    2328 B |\n|            `a.Prop == Static.Method(b.Prop)` |  6,325.2 ns |  67.12 ns |  62.78 ns | 0.6790 |    2864 B |\n|               `a.Prop == (b.Prop \u0026\u0026 c.Prop)` |  6,423.7 ns |  21.64 ns |  18.07 ns | 0.8087 |    3408 B |\n|               `a.Prop == (b.Prop == c.Prop)` |  6,535.5 ns |  49.44 ns |  46.25 ns | 0.8163 |    3432 B |\n|             `a.Prop == (b.Prop \\|\\| c.Prop)` |  6,653.2 ns |  54.95 ns |  51.40 ns | 0.8163 |    3432 B |\n|                 `a.Prop == b.Method(c.Prop)` |  6,823.8 ns |  60.00 ns |  56.13 ns | 0.7553 |    3184 B |\n|               `a.Prop == (b.Prop ?? c.Prop)` |  7,271.6 ns |  53.72 ns |  50.25 ns | 0.8392 |    3536 B |\n|         `a.Prop == b.Method(c.Prop, d.Prop)` |  7,357.3 ns |  50.22 ns |  46.98 ns | 0.8011 |    3360 B |\n| `a.Prop == b.Method(c.Prop, d.Prop, e.Prop)` |  7,552.6 ns |  40.44 ns |  37.83 ns | 0.8392 |    3536 B |\n|                  `a.Prop == b.Prop + c.Prop` |  7,743.0 ns |  77.14 ns |  64.41 ns | 0.8850 |    3736 B |\n|             `a.Prop == $\"{b.Prop}_{c.Prop}\"` |  9,898.2 ns |  20.60 ns |  16.08 ns | 0.9918 |    4176 B |\n|       `a.Prop == (b.Prop + c.Prop).Method()` | 12,735.2 ns |  71.11 ns |  63.04 ns | 0.9155 |    3880 B |\n|        `Static.Method(() =\u003e Method(a.Prop))` | 16,007.6 ns | 131.27 ns | 116.37 ns | 1.2512 |    5344 B |\n\n\u003csup\u003e[sources](EBind.Benchmarks/EBind_Creation.cs)\u003c/sup\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eEBind Trigger \u003csup\u003e\u003ca id='benchmark-ebind-trigger' href='#benchmark-ebind-trigger' title='Anchor'\u003e🔗\u003c/a\u003e\u003c/sup\u003e\u003c/summary\u003e\n\n|                                       Method |      Mean |    Error |   StdDev |  Gen 0 | Allocated |\n|--------------------------------------------- |----------:|---------:|---------:|-------:|----------:|\n|                          `a.Prop == !b.Prop` |  69.32 ns | 0.879 ns | 0.822 ns | 0.0305 |     128 B |\n|                   `a.Prop == b.Prop` // INPC |  72.64 ns | 0.810 ns | 0.758 ns | 0.0305 |     128 B |\n|                                 `a.Method()` |  79.61 ns | 0.653 ns | 0.611 ns | 0.0248 |     104 B |\n|                           `a.Enum == b.Enum` |  80.15 ns | 1.008 ns | 0.943 ns | 0.0362 |     152 B |\n|           `a.Prop == b.Prop` // EventHandler |  82.12 ns | 0.523 ns | 0.489 ns | 0.0076 |      32 B |\n|             `a.Prop == (b.Prop \\|\\| c.Prop)` |  85.83 ns | 0.672 ns | 0.628 ns | 0.0362 |     152 B |\n|               `a.Prop == (b.Prop \u0026\u0026 c.Prop)` |  88.38 ns | 1.761 ns | 1.884 ns | 0.0362 |     152 B |\n|               `a.Prop == (b.Prop == c.Prop)` | 104.50 ns | 1.162 ns | 1.087 ns | 0.0362 |     152 B |\n|               `a.Prop == (b.Prop ?? c.Prop)` | 115.92 ns | 0.883 ns | 0.826 ns | 0.0134 |      56 B |\n|                  `a.Prop == b.Prop + c.Prop` | 122.91 ns | 1.117 ns | 1.045 ns | 0.0200 |      84 B |\n|                           `a.Float == b.Int` | 128.55 ns | 0.769 ns | 0.719 ns | 0.0362 |     152 B |\n|                  `a.Method(b.Prop.Method())` | 128.60 ns | 1.223 ns | 1.144 ns | 0.0324 |     136 B |\n|            `a.Prop == Static.Method(b.Prop)` | 168.87 ns | 2.003 ns | 1.873 ns | 0.0267 |     112 B |\n|                 `a.Prop == b.Method(c.Prop)` | 261.03 ns | 1.913 ns | 1.597 ns | 0.0286 |     120 B |\n|         `a.Prop == b.Method(c.Prop, d.Prop)` | 265.79 ns | 3.130 ns | 2.928 ns | 0.0305 |     128 B |\n| `a.Prop == b.Method(c.Prop, d.Prop, e.Prop)` | 297.89 ns | 2.282 ns | 2.135 ns | 0.0324 |     136 B |\n|             `a.Prop == $\"{b.Prop}_{c.Prop}\"` | 351.95 ns | 2.445 ns | 2.042 ns | 0.0362 |     152 B |\n|       `a.Prop == (b.Prop + c.Prop).Method()` | 417.61 ns | 3.072 ns | 2.724 ns | 0.0515 |     216 B |\n\n\u003csup\u003e[sources](EBind.Benchmarks/EBind_Trigger.cs)\u003c/sup\u003e\n\u003c/details\u003e\n\n## Linking\n\nThe library is linker-safe internally, but some exposed APIs rely on Linq Expression trees and therefore the reflection which have always been hard to process for the [mono linker](https://github.com/mono/linker).\n\nAlthough linker can analyze expression trees and some reflection patterns [pretty well](https://github.com/mono/linker/blob/main/docs/design/reflection-flow.md), the following code units may not be mentioned in the code, appear unused and end up trimmed away:\n* Property setters\n* Events *(which are not configured)*  \n\nThe most common solution for hinting the linker to keep a member is to imitate its usage with a dummy call and mark it with a `[Preserve]` attribute. Your project may already have a `LinkerPleaseInclude.cs` file for that purpose.\n\n[**EBind.LinkerIncludeGenerator**](EBind.LinkerIncludeGenerator) will generate such files for the mentioned members used in `EBinding` and there wont be any `EBind`-related linker issues in your project.  \nAdding its NuGet package is enough for the installation:\n\u003csub\u003e[![NuGet](https://img.shields.io/nuget/v/EBind.LinkerIncludeGenerator?logo=nuget)](https://www.nuget.org/packages/EBind.LinkerIncludeGenerator/)\u003c/sub\u003e\n\n## AOT Compilation \u003csub\u003e[![PLATFORM](https://img.shields.io/badge/platform-Xamarin.iOS-lightgrey)](#aot-compilation-)\u003c/sub\u003e\n\nThis library uses C# 9 function pointers to create fast delegates for property accessors. It's the safest solution for AOT compilation so far.  \nHowever, Xamarin.iOS AOT compiler used for device builds requires a direct indication of value-types as a generic type parameter for them.  \nAll standard structs are [pre-seeded](/EBind/Platform/AotCompilerHints.ios.cs).  \n\nIf you came across an exception like that:\n```\nSystem.ExecutionEngineException:\n  Attempting to JIT compile method 'object EBind.PropertyAccessors.PropertyAccessor`2\u003c..., ...\u003e:Get (object)' while running in aot-only mode.\n```\nplease add a hint for the compiler:\n```cs\nEBind.Platform.AotCompilerHints.Include\u003cMyStruct\u003e(); // custom struct as a member\n// or\nEBind.Platform.AotCompilerHints.Include\u003cMyStruct, PropertyType\u003e(); // custom struct as a target\n```\n\n## Contributions\n\nIf you've found an error, please file an issue.\n\nPatches are encouraged and may be submitted by forking this project and submitting a pull request.  \nIf your change is substantial, please raise an issue or start a discussion first.\n\n## Development\n\n### Requirements\n\n* C# 9 and .NET 5 workload\n* Xamarin.Android and Xamarin.iOS SDKs for device testing\n\n### Device Testing\n\nDevice tests may run manually from the test app UI or automatically with [XHarness CLI](https://github.com/dotnet/xharness).\n\nAndroid:\n```bash\nxharness android test \\\n  --app=\"./EBind.Tests.Android/bin/Release/EBind.Tests.Android-Signed.apk\" \\\n  --package-name=\"EBind.Tests.Android\" \\\n  --instrumentation=\"ebind.tests.droid.xharness_instrumentation\" \\\n  --device-arch=\"x86\" \\\n  --output-directory=\"./EBind.Tests.Android/TestResults/xharness_android\" \\\n  --verbosity\n```\n\niOS:\n```bash\nxharness apple test \\\n  --app=\"./EBind.Tests.iOS/bin/iPhoneSimulator/Release/EBind.Tests.iOS.app\" \\\n  --output-directory=\"./EBind.Tests.iOS/TestResults/xharness_apple\" \\\n  --targets=ios-simulator-64 \\\n  --verbosity \\\n  -- -app-arg:xharness # to be passed in Application.Main(string[])\n```\n\nIt's also a part of the github actions workflow. [Check it out!](.github/workflows/ci.yml)\n\n## The Story\n\nOnce upon a time, there was a small but powerful library called `Bind` from the Praeclarum family.  \n\nNo other library could be compared with it in terms of concision and beauty. Every developer was dreaming about using it in his project. But it had several problems and missed some optimizations. The bravest of us could dare to use it in production.  \n\nBeauty was stronger than fear and this is how another fork has begun.  \nBugs were fixed, some optimizations were made but it was never enough. That library deserved to SHINE!\n\nSo...\n\n- Factory method `Create` replaced with a collection initializer to make bindings a separate block and utilize more syntax variations.\n- Binding chaining became redundant, operator `\u0026\u0026` can be used in binding expressions now.\n- Added flags that alter binding behavior, as a part of the collection initializer.\n- Added support for binding properties/fields to functions ([Action bindings](#action)).\n- Added support for binding functions and commands to events ([Event bindings](#event)).\n- Binding triggers made configurable, all the basic ones for Xamarin are pre-defined.\n- Manual invalidation doesn't seem necessary anymore – removed.\n- Thread-safety improved by the means of dispatchers and concurrent collections.\n- Performance brought to another level with expression interpretation, fast delegates (incl. C# 9 function pointers), and a different architecture.\n- The library became more strict and informative in terms of exceptions and errors.\n- Added a Source Generator that hints the mono linker about code usage.\n- `Unbind` replaced by `IDisposable`, useful for aggregation (e.g. `CompositeDisposable`).\n\nIt ended up being completely rewritten with only some tests remained.\n\n## Credits\n\n* [@praeclarum](https://github.com/praeclarum) \u0026 [Praeclarum.Bind](https://github.com/praeclarum/Bind) - :heart: Code and Inspiration\n\n## License\n\nThis project is licensed under the Apache License, Version 2.0 - see the [LICENSE](LICENSE) and [NOTICE](NOTICE) files for details.\n","funding_links":[],"categories":["MVVM","Validation / DataBinding / MVx / Interactions"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSIDOVSKY%2FEBind","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSIDOVSKY%2FEBind","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSIDOVSKY%2FEBind/lists"}