{"id":15109579,"url":"https://github.com/reactivemarbles/dynamicdata","last_synced_at":"2025-12-15T00:29:48.789Z","repository":{"id":23583227,"uuid":"26951509","full_name":"reactivemarbles/DynamicData","owner":"reactivemarbles","description":"Reactive collections based on Rx.Net","archived":false,"fork":false,"pushed_at":"2025-04-11T14:55:06.000Z","size":19041,"stargazers_count":1793,"open_issues_count":30,"forks_count":184,"subscribers_count":63,"default_branch":"main","last_synced_at":"2025-04-11T15:54:48.574Z","etag":null,"topics":["csharp","dotnet","dynamic-data","dynamicdata","hacktoberfest","mvvm","reactive-collections","reactive-extensions","reactive-programming","reactivex","rx","rx-observable"],"latest_commit_sha":null,"homepage":"http://dynamic-data.org","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/reactivemarbles.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},"funding":{"github":"reactivemarbles"}},"created_at":"2014-11-21T08:15:58.000Z","updated_at":"2025-04-10T08:41:05.000Z","dependencies_parsed_at":"2023-10-20T22:47:37.580Z","dependency_job_id":"b82edef6-c6b0-4b31-af3c-b7fb1efc74dc","html_url":"https://github.com/reactivemarbles/DynamicData","commit_stats":null,"previous_names":["rolandpheasant/dynamicdata"],"tags_count":119,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactivemarbles%2FDynamicData","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactivemarbles%2FDynamicData/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactivemarbles%2FDynamicData/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactivemarbles%2FDynamicData/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reactivemarbles","download_url":"https://codeload.github.com/reactivemarbles/DynamicData/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248475975,"owners_count":21110188,"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","dotnet","dynamic-data","dynamicdata","hacktoberfest","mvvm","reactive-collections","reactive-extensions","reactive-programming","reactivex","rx","rx-observable"],"created_at":"2024-09-25T23:05:09.153Z","updated_at":"2025-12-15T00:29:48.731Z","avatar_url":"https://github.com/reactivemarbles.png","language":"C#","readme":"![Build](https://github.com/reactivemarbles/DynamicData/workflows/Build/badge.svg) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=reactivemarbles_DynamicData\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=reactivemarbles_DynamicData) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=reactivemarbles_DynamicData\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=reactivemarbles_DynamicData) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=reactivemarbles_DynamicData\u0026metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=reactivemarbles_DynamicData) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=reactivemarbles_DynamicData\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=reactivemarbles_DynamicData) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=reactivemarbles_DynamicData\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=reactivemarbles_DynamicData)\n\u003ca href=\"https://reactiveui.net/slack\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/chat-slack-blue.svg\"\u003e\n\u003c/a\u003e\n[![NuGet Stats](https://img.shields.io/nuget/v/DynamicData.svg)](https://www.nuget.org/packages/DynamicData) ![Downloads](https://img.shields.io/nuget/dt/DynamicData.svg)\n\u003cbr /\u003e\n\u003cbr /\u003e\n\u003ca href=\"https://github.com/reactiveui/DynamicData\"\u003e\n        \u003cimg width=\"170\" height=\"170\" src=\"https://github.com/reactiveui/styleguide/blob/master/logo_dynamic_data/logo.svg\"/\u003e\n\u003c/a\u003e\n\n## Dynamic Data\n\nDynamic Data is a portable class library which brings the power of Reactive Extensions (Rx) to collections.  \n\nRx is extremely powerful but out of the box provides nothing to assist with managing collections.  In most applications there is a need to update the collections dynamically.  Typically a collection is loaded and after the initial load, asynchronous updates are received.  The original collection will need to reflect these changes. In simple scenarios the code is simple. However, typical applications are much more complicated and may apply a filter, transform the original dto and apply a sort. Even with these simple every day operations the complexity of the code is quickly magnified.  Dynamic data has been developed to remove the tedious code of dynamically maintaining collections. It has grown to become functionally very rich with at least 60 collection based operations which amongst other things enable filtering, sorting, grouping,  joining different sources,  transforms, binding, pagination, data virtualisation, expiration, disposal management plus more.  \n\nThe concept behind using dynamic data is you maintain a data source (either ```SourceCache\u003cTObject, TKey\u003e``` or  ```SourceList\u003cTObject\u003e```),  then chain together various combinations of operators to declaratively manipulate and shape the data without the need to directly manage any collection.   \n\nAs an example the following code will filter trades to select only live trades, creates a proxy for each live trade, and finally orders the results by most recent first. The resulting trade proxies are bound on the dispatcher thread to an observable collection.  Also since  the proxy is disposable ```DisposeMany()``` will ensure the proxy is disposed when no longer used.\n\n```cs\nReadOnlyObservableCollection\u003cTradeProxy\u003e list;\n\nvar myTradeCache = new SourceCache\u003cTrade, long\u003e(trade =\u003e trade.Id);\nvar myOperation = myTradeCache.Connect() \n\t\t.Filter(trade=\u003etrade.Status == TradeStatus.Live) \n\t\t.Transform(trade =\u003e new TradeProxy(trade))\n\t\t.Sort(SortExpressionComparer\u003cTradeProxy\u003e.Descending(t =\u003e t.Timestamp))\n\t\t.ObserveOnDispatcher()\n\t\t.Bind(out list) \n\t\t.DisposeMany()\n\t\t.Subscribe()\n```\nThe magic is that as  ```myTradeCache``` is maintained the target observable collection looks after itself.\n\nThis is a simple example to show how using Dynamic Data's collections and operators make in-memory data management extremely easy and can reduce the size and complexity of your code base by abstracting complicated and often repetitive operations.\n\n### Sample Projects \n\n- Sample WPF project trading project [Dynamic Trader](https://github.com/RolandPheasant/Dynamic.Trader)\n- Various unit tested examples of many different operators [Snippets](https://github.com/RolandPheasant/DynamicData.Snippets)\n- [Tail Blazer](https://github.com/RolandPheasant/TailBlazer) for tailing files \n\n### Get in touch \n\nIf you have any questions, want to get involved or would simply like to keep abreast of developments, you are welcome to join the slack community [Reactive UI Slack](https://reactiveui.net/slack). I am also available [@RolandPheasant](https://twitter.com/RolandPheasant) \nThere is a blog at  https://dynamic-data.org/ but alas it is hopelessly out of date.\n\n## Table of Contents\n\n* [Dynamic Data](#dynamic-data)\n  * [Sample Projects](#sample-projects)\n  * [Get in touch](#get-in-touch)\n* [Table of Contents](#table-of-contents)\n* [Create Dynamic Data Collections](#create-dynamic-data-collections)\n  * [The Observable List](#the-observable-list)\n  * [The Observable Cache](#the-observable-cache)\n* [Creating Observable Change Sets](#creating-observable-change-sets)\n  * [Connect to a Cache or List](#connect-to-a-cache-or-list)\n  * [Create an Observable Change Set from an Rx Observable](#create-an-observable-change-set-from-an-rx-observable)\n  * [Create an Observable Change Set from an Rx Observable with an Expiring Cache](#create-an-observable-change-set-from-an-rx-observable-with-an-expiring-cache)\n  * [Create an Observable Change Set from an Observable Collection](#create-an-observable-change-set-from-an-observable-collection)\n  * [Create an Observable Change Set from an Binding List](#create-an-observable-change-set-from-an-binding-list)\n  * [Using the ObservableChangeSet static class](#using-the-observablechangeset-static-class)\n* [Consuming Observable Change Sets](#consuming-observable-change-sets)\n* [Observable list vs observable cache](#observable-list-vs-observable-cache)\n* [History of Dynamic Data](#history-of-dynamic-data)\n* [Want to know more?](#want-to-know-more)\n\n## Create Dynamic Data Collections\n\n### The Observable List\n\nCreate an observable list like this:\n```cs\nvar myInts = new SourceList\u003cint\u003e();\n```\nThe observable list provides the direct edit methods you would expect. For example:\n```cs\nmyInts.AddRange(Enumerable.Range(0, 10000)); \nmyInts.Add(99999); \nmyInts.Remove(99999);\n```\nThe `AddRange`, `Add` and `Remove` methods above will each produce a distinct change notification.  In order to increase efficiency when making multiple amendments, the list provides a means of batch editing. This is achieved using the `.Edit` method which ensures only a single change notification is produced.\n```cs\nmyInts.Edit(innerList =\u003e\n{\n   innerList.Clear();\n   innerList.AddRange(Enumerable.Range(0, 10000));\n});\n```\nIf ``myInts`` is to be exposed publicly it can be made read only using `.AsObservableList`\n```cs\nIObservableList\u003cint\u003e readonlyInts = myInts.AsObservableList();\n```\nwhich hides the edit methods.\n\nThe list's changes can be observed by calling `myInts.Connect()` like this:\n```cs\nIObservable\u003cIChangeSet\u003cint\u003e\u003e myIntsObservable = myInts.Connect();\n```\nThis creates an observable change set for which there are dozens of operators. The changes are transmitted as an Rx observable, so they are fluent and composable.\n\n### The Observable Cache\n\nCreate an observable cache like this:\n```cs\nvar myCache = new SourceCache\u003cTObject,TKey\u003e(t =\u003e key);\n```\nThere are direct edit methods, for example\n\n```cs\nmyCache.Clear();\nmyCache.AddOrUpdate(myItems);\n```\nThe `Clear` and `AddOrUpdate` methods above will each produce a distinct change notification.  In order to increase efficiency when making multiple amendments, the cache provides a means of batch editing. This is achieved using the `.Edit` method which ensures only a single change notification is produced.\n\n```cs\nmyCache.Edit(innerCache =\u003e\n\t\t\t  {\n\t\t\t      innerCache.Clear();\n\t\t\t      innerCache.AddOrUpdate(myItems);\n\t\t\t  });\n```\nIf `myCache` is to be exposed publicly it can be made read only using `.AsObservableCache`\n\n```cs\nIObservableCache\u003cTObject,TKey\u003e readonlyCache = myCache.AsObservableCache();\n```\nwhich hides the edit methods.\n\nThe cache is observed by calling `myCache.Connect()` like this:\n```cs\nIObservable\u003cIChangeSet\u003cTObject,TKey\u003e\u003e myCacheObservable = myCache.Connect();\n```\nThis creates an observable change set for which there are dozens of operators. The changes are transmitted as an Rx observable, so they are fluent and composable.\n\n## Creating Observable Change Sets\nAs stated in the introduction of this document, Dynamic Data is based on the concept of creating and manipulating observable change sets. \n\nThe primary method of creating observable change sets is to connect to instances of `ISourceCache\u003cT,K\u003e` and `ISourceList\u003cT\u003e`. There are alternative methods to produce observables change sets however, depending on the data source.\n\n### Connect to a Cache or List\nCalling `Connect()` on a `ISourceList\u003cT\u003e` or `ISourceCache\u003cT,K\u003e` will produce an observable change set. \n```cs\nvar myObservableChangeSet = myDynamicDataSource.Connect();\n```\n\n### Create an Observable Change Set from an Rx Observable\nGiven either of the following observables:\n```cs\nIObservable\u003cT\u003e myObservable;\nIObservable\u003cIEnumerable\u003cT\u003e\u003e myObservable;\n```\nan observable change set can be created like by calling `.ToObservableChangeSet` like this:\n```cs\nvar myObservableChangeSet = myObservable.ToObservableChangeSet(t=\u003e t.key);\n```\n\n### Create an Observable Change Set from an Rx Observable with an Expiring Cache\nThe problem with the example above is that the internal backing cache of the observable change set will grow in size forever. \nTo counter this behavior, there are overloads of `.ToObservableChangeSet` where a size limitation or expiry time can be specified for the internal cache.\n\nTo create a time expiring cache, call `.ToObservableChangeSet` and specify the expiry time using the expireAfter argument:\n```cs\nvar myConnection = myObservable.ToObservableChangeSet(t=\u003e t.key, expireAfter: item =\u003e TimeSpan.FromHours(1));\n```\n\nTo create a size limited cache, call `.ToObservableChangeSet` and specify the size limit using the limitSizeTo argument:\n```cs\nvar myConnection = myObservable.ToObservableChangeSet(t=\u003e t.key, limitSizeTo:10000);\n```\nThere is also an overload to specify expiration by both time and size.\n\n### Create an Observable Change Set from an Observable Collection\n```cs\nvar myObservableCollection = new ObservableCollection\u003cT\u003e();\n```\nTo create a cache based observable change set, call `.ToObservableChangeSet` and specify a key selector for the backing cache\n```cs\nvar myConnection = myObservableCollection.ToObservableChangeSet(t =\u003e t.Key);\n```\nor to create a list based observable change set call `.ToObservableChangeSet` with no arguments\n```cs\nvar myConnection = myObservableCollection.ToObservableChangeSet();\n```\nThis method is only recommended for simple queries which act only on the UI thread as `ObservableCollection` is not thread safe.\n\n### Create an Observable Change Set from an Binding List\n```cs\nvar myBindingList = new BindingList\u003cT\u003e();\n```\nTo create a cache based observable change set, call `.ToObservableChangeSet` and specify a key selector for the backing cache\n```cs\nvar myConnection = myBindingList.ToObservableChangeSet(t =\u003e t.Key);\n```\nor to create a list based observable change set call `.ToObservableChangeSet` with no arguments\n```cs\nvar myConnection = myBindingList.ToObservableChangeSet();\n```\nThis method is only recommended for simple queries which act only on the UI thread as `ObservableCollection` is not thread safe.\n\n### Using the ObservableChangeSet static class\n\nThere is also  another way to create observable change sets, and that is to use the ```ObservableChangeSet``` static class.  This class is a facsimile of the Rx.Net ```Observable``` static class and provides an almost identical API. \n\nAn observable list can be created as follows:\n\n```cs\n  var myObservableList = ObservableChangeSet.Create\u003cint\u003e(observableList =\u003e\n  {\n\t  //some code to load data and subscribe\n      var loader= myService.LoadMyDataObservable().Subscribe(observableList.Add);\n      var subscriber = myService.GetMySubscriptionsObservable().Subscribe(observableList.Add);\n      //dispose of resources\n      return new CompositeDisposable(loader,subscriber );\n  });\n```\nand creating a cache is almost identical except a key has to be specified \n```cs\n  var myObservableCache = ObservableChangeSet.Create\u003cTrade, int\u003e(observableCache =\u003e\n  {\n\t  //code omitted\n  }, trade = \u003e trade.Id);\n```\nThere are several overloads ```ObservableChangeSet.Create``` which match the overloads which ```Observable.Create``` provides.\n\n## Consuming Observable Change Sets\nThe examples below illustrate the kind of things you can achieve after creating an observable change set. \nNow you can create an observable cache or an observable list, here are a few quick fire examples to illustrate the diverse range of things you can do. In all of these examples the resulting sequences always exactly reflect the items is the cache i.e. adds, updates and removes are always propagated.\n\n#### Create a Derived List or Cache\nThis example shows how you can create derived collections from an observable change set. It applies a filter to a collection, and then creates a new observable collection that only contains items from the original collection that pass the filter.\nThis pattern is incredibly useful when you want to make modifications to an existing collection and then expose the modified collection to consumers. \n\nEven though the code in this example is very simple, this is one of the most powerful aspects of Dynamic Data. \n\nGiven a SourceList \n```cs\nvar myList = new SourceList\u003cPeople\u003e();\n```\nYou can apply operators, in this case the `Filter()` operator, and then create a new observable list with `AsObservableList()`\n```cs\nvar oldPeople = myList.Connect().Filter(person =\u003e person.Age \u003e 65).AsObservableList();\n```\nThe resulting observable list, oldPeople, will only contain people who are older than 65.\n\nThe same pattern can be used with SourceCache by using `.AsObservableCache()` to create derived caches.\n\nAs an alternative to `.Bind(out collection)` you can use `.BindToObservableList(out observableList)` for both `SourceList` \u0026 `SourceCache`. This is useful for getting derived read-only lists from sources that use `.AutoRefresh()`, since collections do not support refresh notifications.\n\n#### Filtering\nFilter the observable change set by using the `Filter` operator\n```cs\nvar myPeople = new SourceList\u003cPeople\u003e();\nvar myPeopleObservable = myPeople.Connect();\n\nvar myFilteredObservable = myPeopleObservable.Filter(person =\u003e person.Age \u003e 50); \n```\nor to filter a change set dynamically \n```cs\nIObservable\u003cFunc\u003cPerson,bool\u003e\u003e observablePredicate=...;\nvar myFilteredObservable = myPeopleObservable.Filter(observablePredicate); \n```\n\n#### Sorting\nSort the observable change set by using the `Sort` operator\n```cs\nvar myPeople = new SourceList\u003cPeople\u003e();\nvar myPeopleObservable = myPeople.Connect();\nvar mySortedObservable = myPeopleObservable.Sort(SortExpressionComparer.Ascending(p =\u003e p.Age)); \n```\nor to dynamically change sorting\n```cs\nIObservable\u003cIComparer\u003cPerson\u003e\u003e observableComparer=...;\nvar mySortedObservable = myPeopleObservable.Sort(observableComparer);\n```\nFor more information on sorting see [wiki](https://github.com/RolandPheasant/DynamicData/wiki/Sorting)\n\n#### Grouping\nThe `GroupOn` operator pre-caches the specified groups according to the group selector.\n```cs\nvar myOperation = personChangeSet.GroupOn(person =\u003e person.Status)\n```\nThe value of the inner group is represented by an observable list for each matched group. When values matching the inner grouping are modified, it is the inner group which produces the changes.\nYou can also use `GroupWithImmutableState` which will produce a grouping who's inner items are a fixed size array.\n\n#### Transformation\nThe `Transform` operator allows you to map objects from the observable change set to another object\n```cs\nvar myPeople = new SourceList\u003cPeople\u003e();\nvar myPeopleObservable = myPeople.Connect();\nvar myTransformedObservable = myPeopleObservable.Transform(person =\u003e new PersonProxy(person));\n```\n\nThe `TransformToTree` operator allows you to create a fully formed reactive tree (only available for observable cache)\n```cs\nvar myPeople = new SourceCache\u003cPerson, string\u003e(p =\u003e p.Name);\nvar myTransformedObservable = myPeople.Connect().TransformToTree(person =\u003e person.BossId);\n```\n\n\nFlatten a child enumerable\n```cs\nvar myOperation = personChangeSet.TransformMany(person =\u003e person.Children) \n```\n\n#### Aggregation\nThe `Count`, `Max`, `Min`, `Avg`, and `StdDev` operators allow you to perform aggregate functions on observable change sets\n```cs\nvar myPeople = new SourceList\u003cPeople\u003e();\nvar myPeopleObservable = myPeople.Connect();\n\nvar countObservable = \t myPeopleObservable.Count();\nvar maxObservable = \t myPeopleObservable.Max(p =\u003e p.Age);\nvar minObservable = \t myPeopleObservable.Min(p =\u003e p.Age);\nvar stdDevObservable =   myPeopleObservable.StdDev(p =\u003e p.Age);\nvar avgObservable = \t myPeopleObservable.Avg(p =\u003e p.Age);\n```\nMore aggregating operators will be added soon.\n\n#### Logical Operators\nThe `And`, `Or`, `Xor` and `Except` operators allow you to perform logical operations on observable change sets\n```cs\nvar peopleA = new SourceCache\u003cPerson,string\u003e(p =\u003e p.Name);\nvar peopleB = new SourceCache\u003cPerson,string\u003e(p =\u003e p.Name);\n\nvar observableA = peopleA.Connect();\nvar observableB = peopleB.Connect();\n\nvar inBoth = observableA.And(observableB);\nvar inEither= observableA.Or(observableB);\nvar inOnlyOne= observableA.Xor(observableB);\nvar inAandNotinB = observableA.Except(observableB);\n```\n\nA recent and very powerful feature is dynamic logical operators. From version 4.6 onwards you can dynamically include and exclude collections from the resulting list. \n```cs\nvar list1 = new SourceList\u003cint\u003e();\nvar list2 = new SourceList\u003cint\u003e();\nvar list3  = new SourceList\u003cint\u003e();\n\t\nvar combined = new SourceList\u003cISourceList\u003cint\u003e\u003e();\n\n//child lists can be added or removed any time\ncombined.Add(list1);\ncombined.Add(list2);\ncombined.Add(list3);\n\n//The operators look after themselves \nvar inAll = combined.And();\nvar inAny = combined.Or();\nvar inOnlyOne= combined.Xor();\nvar inFirstAndNotAnyOther = combined.Except();\n```\nFor more information on grouping see [wiki](https://github.com/RolandPheasant/DynamicData/wiki/Composite-Collections)\n \n\n#### Disposal\nThe `DisposeMany` operator ensures that objects are disposed when removed from an observable stream\n```cs\nvar myPeople = new SourceList\u003cPeople\u003e();\nvar myPeopleObservable = myPeople.Connect();\nvar myTransformedObservable = myPeopleObservable.Transform(person =\u003e new DisposablePersonProxy(person))\n                                                .DisposeMany();\n```\nThe `DisposeMany` operator is typically used when a transform function creates disposable objects.\n\n#### Distinct Values\nThe `DistinctValues` operator will select distinct values from the underlying collection\n```cs\nvar myPeople = new SourceList\u003cPeople\u003e();\nvar myPeopleObservable = myPeople.Connect();\nvar myDistinctObservable = myPeopleObservable.DistinctValues(person =\u003e person.Age);\n```\n\n#### Virtualisation\n\nVirtualise data to restrict by index and segment size\n```cs\nIObservable\u003cIVirtualRequest\u003e request; //request stream\nvar virtualisedStream = someDynamicDataSource.Virtualise(request)\n```\nVirtualise data to restrict by index and page size\n```cs\nIObservable\u003cIPageRequest\u003e request; //request stream\nvar pagedStream = someDynamicDataSource.Page(request)\n```\nIn either of the above, the result is re-evaluated when the request stream changes\n\nTop is an overload of ```Virtualise()``` and will return items matching the first 'n'  items.\n```cs\nvar topStream = someDynamicDataSource.Top(10)\n```\n\n#### Observing Properties of Objects in a Collection\nIf the collection is made up of objects that implement `INotifyPropertyChanged` then the following operators are available\n\nThe `WhenValueChanged` operator returns an observable of the value of the specified property when it has changed\n```cs\nvar ageChanged = peopleDataSource.Connect().WhenValueChanged(p =\u003e p.Age)\n```\n\nThe `WhenPropertyChanged` operator returns an observable made up of the value of the specified property as well as it's parent object when the specified property has changed\n```cs\nvar ageChanged = peopleDataSource.Connect().WhenPropertyChanged(p =\u003e p.Age)\n```\n\nThe `WhenAnyPropertyChanged` operator returns an observable of objects when any of their properties have changed\n```cs\nvar personChanged = peopleDataSource.Connect().WhenAnyPropertyChanged()\n```\n\n#### Observing item changes\n\nBinding is a very small part of Dynamic Data. The above notify property changed overloads are just an example when binding. If you have a domain object which has children observables you can use ```MergeMany()``` which subscribes to and unsubscribes from items according to collection changes.\n\n```cs\nvar myoperation = somedynamicdatasource.Connect() \n\t\t\t.MergeMany(trade =\u003e trade.SomeObservable());\n```\nThis wires and unwires ```SomeObservable``` as the collection changes.\n\n## Observable list vs observable cache\nI get asked about the differences between these a lot and the answer is really simple. If you have a unique id, you should use an observable cache as it is dictionary based which will ensure no duplicates can be added and it notifies on adds, updates and removes, whereas list allows duplicates and only has no concept of an update.\n\nThere is another difference. The cache side of dynamic data is much more mature and has a wider range of operators. Having more operators is mainly because I found it easier to achieve good all round performance with the key based operators and do not want to add anything to Dynamic Data which inherently has poor performance.\n\n## History of Dynamic Data\nEven before Rx existed I had implemented a similar concept using old fashioned events but the code was very ugly and my implementation full of race conditions so it never existed outside of my own private sphere. My second attempt was a similar implementation to the first but using Rx when it first came out. This also failed as my understanding of Rx was flawed and limited and my design forced consumers to implement interfaces.  Then finally I got my design head on and in 2011-ish I started writing what has become dynamic data. No inheritance, no interfaces, just the ability to plug in and use it as you please.  All along I meant to open source it but having so utterly failed on my first 2 attempts I decided to wait until the exact design had settled down. The wait lasted longer than I expected and ended up taking over 2 years but the benefit is it has been trialled for 2 years on a very busy high volume low latency trading system which has seriously complicated data management. And what's more that system has gathered a load of attention for how slick and cool and reliable it is both from the user and IT point of view. So I present this library with the confidence of it being tried, tested, optimised and mature. I hope it can make your life easier like it has done for me.\n\n## Want to know more?\nI could go on endlessly but this is not the place for full documentation.  I promise this will come but for now I suggest downloading my WPF sample app (links at top of document)  as I intend it to be a 'living document' and I promise it will be continually maintained. \n\nAlso, if you follow me on Twitter you will find out when new samples or blog posts have been updated.\n\nAdditionally, if you have read up to here and not pressed star then why not? Ha. A star may make me be more responsive to any requests or queries.\n","funding_links":["https://github.com/sponsors/reactivemarbles"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freactivemarbles%2Fdynamicdata","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freactivemarbles%2Fdynamicdata","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freactivemarbles%2Fdynamicdata/lists"}