├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.yml
│ ├── config.yml
│ └── feature_request.yml
├── renovate.json
└── workflows
│ ├── ci-build.yml
│ ├── lock.yml
│ ├── pr-test-coverage.yml
│ └── release.yml
├── .gitignore
├── LICENSE
├── NonProduction
└── DynamicData.Profile
│ ├── Allocations.cs
│ ├── AllocationsCount.cs
│ ├── CacheAllocationChecks.cs
│ ├── CheapAndDirtyAllocationTest.cs
│ ├── DynamicData.Profile.csproj
│ └── Person.cs
├── README.md
├── ReleaseNotes.md
├── images
├── dd-logo.svg
├── dd-shapes.svg
└── logo.png
├── nuget.config
├── src
├── Directory.Build.props
├── Directory.build.targets
├── DynamicData.Benchmarks
│ ├── .editorconfig
│ ├── Cache
│ │ ├── DisposeMany_Cache.cs
│ │ ├── EditDiff.cs
│ │ ├── ExpireAfter_Cache_ForSource.cs
│ │ ├── ExpireAfter_Cache_ForStream.cs
│ │ ├── FilterImmutable.cs
│ │ ├── Filter_Cache_WithPredicateState.cs
│ │ ├── SortAndBindChange.cs
│ │ ├── SortAndBindInitial.cs
│ │ ├── SourceCache.cs
│ │ ├── StatelessFiltering.cs
│ │ ├── StatelessTransforming.cs
│ │ ├── ToObservableChangeSet_Cache.cs
│ │ ├── TransformImmutable.cs
│ │ └── TransformMany.cs
│ ├── DynamicData.Benchmarks.csproj
│ ├── List
│ │ ├── DisposeMany_List.cs
│ │ ├── ExpireAfter_List.cs
│ │ ├── Filter_List_WithPredicateState.cs
│ │ ├── GroupAdd.cs
│ │ ├── GroupRemove.cs
│ │ ├── SourceList.cs
│ │ └── ToObservableChangeSet_List.cs
│ ├── Polyfills
│ │ ├── CompilerFeatureRequiredAttribute.cs
│ │ ├── IsExternalInit.cs
│ │ └── RequiredMemberAttribute.cs
│ └── Program.cs
├── DynamicData.Tests
│ ├── API
│ │ ├── ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt
│ │ ├── ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt
│ │ ├── ApiApprovalTests.cs
│ │ └── ApiExtensions.cs
│ ├── AggregationTests
│ │ ├── AggregationFixture.cs
│ │ ├── AverageFixture.cs
│ │ ├── MaxFixture.cs
│ │ ├── MinFixture.cs
│ │ └── SumFixture.cs
│ ├── AutoRefreshFilter.cs
│ ├── Binding
│ │ ├── AvaloniaDictionaryFixture.cs
│ │ ├── BindingLIstBindListFixture.cs
│ │ ├── BindingListBindCacheFixture.cs
│ │ ├── BindingListBindCacheSortedFixture.cs
│ │ ├── BindingListToChangeSetFixture.cs
│ │ ├── DeeplyNestedNotifyPropertyChangedFixture.cs
│ │ ├── IObservableListBindCacheFixture.cs
│ │ ├── IObservableListBindCacheSortedFixture.cs
│ │ ├── IObservableListBindListFixture.cs
│ │ ├── NotifyPropertyChangedExFixture.cs
│ │ ├── ObservableCollectionBindCacheFixture.cs
│ │ ├── ObservableCollectionBindCacheSortedFixture.cs
│ │ ├── ObservableCollectionBindListFixture.cs
│ │ ├── ObservableCollectionExtendedToChangeSetFixture.cs
│ │ ├── ObservableCollectionToChangeSetFixture.cs
│ │ ├── ReadOnlyObservableCollectionToChangeSetFixture.cs
│ │ └── ReadonlyCollectionBindCacheFixture.cs
│ ├── Cache
│ │ ├── AndFixture.cs
│ │ ├── AsyncDisposeManyFixture.cs
│ │ ├── AutoRefreshFixture.cs
│ │ ├── BatchFixture.cs
│ │ ├── BatchIfFixture.cs
│ │ ├── BatchIfWithTimeOutFixture.cs
│ │ ├── BufferInitialFixture.cs
│ │ ├── DeferUntilLoadedFixture.cs
│ │ ├── DisposeManyFixture.cs
│ │ ├── DistinctFixture.cs
│ │ ├── DynamicAndFixture.cs
│ │ ├── DynamicExceptFixture.cs
│ │ ├── DynamicOrFixture.cs
│ │ ├── DynamicXorFixture.cs
│ │ ├── EditDiffChangeSetFixture.cs
│ │ ├── EditDiffChangeSetOptionalFixture.cs
│ │ ├── EditDiffFixture.cs
│ │ ├── EnsureUniqueKeysFixture.cs
│ │ ├── EnumerableObservableToObservableChangeSetFixture.cs
│ │ ├── ExceptFixture.cs
│ │ ├── ExpireAfterFixture.ForSource.cs
│ │ ├── ExpireAfterFixture.ForStream.cs
│ │ ├── ExpireAfterFixture.cs
│ │ ├── FilterControllerFixture.cs
│ │ ├── FilterFixture.WithPredicateState.cs
│ │ ├── FilterFixture.cs
│ │ ├── FilterImmutableFixture.cs
│ │ ├── FilterOnConnectFixture.cs
│ │ ├── FilterOnObservableFixture.cs
│ │ ├── FilterOnPropertyFixture.cs
│ │ ├── FilterParallelFixture.cs
│ │ ├── ForEachChangeFixture.cs
│ │ ├── FromAsyncFixture.cs
│ │ ├── FullJoinFixture.cs
│ │ ├── FullJoinManyFixture.cs
│ │ ├── GroupControllerFixture.cs
│ │ ├── GroupControllerForFilteredItemsFixture.cs
│ │ ├── GroupFixture.cs
│ │ ├── GroupFromDistinctFixture.cs
│ │ ├── GroupImmutableFixture.cs
│ │ ├── GroupOnDynamicFixture.cs
│ │ ├── GroupOnObservableFixture.cs
│ │ ├── GroupOnPropertyFixture.cs
│ │ ├── GroupOnPropertyWithImmutableStateFixture.cs
│ │ ├── IgnoreUpdateFixture.cs
│ │ ├── IncludeUpdateFixture.cs
│ │ ├── InnerJoinFixture.cs
│ │ ├── InnerJoinFixtureRaceCondition.cs
│ │ ├── InnerJoinManyFixture.cs
│ │ ├── KeyValueCollectionEx.cs
│ │ ├── LeftJoinFixture.cs
│ │ ├── LeftJoinManyFixture.cs
│ │ ├── MergeChangeSetsFixture.cs
│ │ ├── MergeManyChangeSetsCacheFixture.cs
│ │ ├── MergeManyChangeSetsCacheSourceCompareFixture.cs
│ │ ├── MergeManyChangeSetsListFixture.cs
│ │ ├── MergeManyFixture.cs
│ │ ├── MergeManyItemsFixture.cs
│ │ ├── MergeManyWithKeyOverloadFixture.cs
│ │ ├── MonitorStatusFixture.cs
│ │ ├── ObservableCachePreviewFixture.cs
│ │ ├── ObservableChangeSetFixture.cs
│ │ ├── ObservableToObservableChangeSetFixture.cs
│ │ ├── OfTypeFixture.cs
│ │ ├── OnItemFixture.cs
│ │ ├── OrFixture.cs
│ │ ├── PageFixture.cs
│ │ ├── QueryWhenChangedFixture.cs
│ │ ├── RefCountFixture.cs
│ │ ├── RightJoinFixture.cs
│ │ ├── RightJoinManyFixture.cs
│ │ ├── SizeLimitFixture.cs
│ │ ├── SortAndBindFixture.cs
│ │ ├── SortAndBindObservableFixture.cs
│ │ ├── SortAndPageAndBindFixture.cs
│ │ ├── SortAndPageFixture.cs
│ │ ├── SortAndVirtualizeAndBindFixture.cs
│ │ ├── SortAndVirtualizeFixture.cs
│ │ ├── SortFixture.cs
│ │ ├── SortObservableFixtureFixture.cs
│ │ ├── SourceCacheFixture.cs
│ │ ├── SubscribeManyFixture.cs
│ │ ├── SuspendNotificationsFixture.cs
│ │ ├── SwitchFixture.cs
│ │ ├── ToObservableChangeSetFixture.cs
│ │ ├── ToObservableOptionalFixture.cs
│ │ ├── ToSortedCollectionFixture.cs
│ │ ├── TransformAsyncFixture.cs
│ │ ├── TransformFixture.cs
│ │ ├── TransformFixtureParallel.cs
│ │ ├── TransformImmutableFixture.cs
│ │ ├── TransformManyAsyncFixture.cs
│ │ ├── TransformManyFixture.cs
│ │ ├── TransformManyObservableCacheFixture.cs
│ │ ├── TransformManyRefreshFixture.cs
│ │ ├── TransformManySimpleFixture.cs
│ │ ├── TransformOnObservableFixture.cs
│ │ ├── TransformSafeAsyncFixture.cs
│ │ ├── TransformSafeFixture.cs
│ │ ├── TransformSafeParallelFixture.cs
│ │ ├── TransformTreeFixture.cs
│ │ ├── TransformTreeWithRefreshFixture.cs
│ │ ├── TransformWithInlineUpdateFixture.cs
│ │ ├── TrueForAllFixture.cs
│ │ ├── TrueForAnyFixture.cs
│ │ ├── WatchFixture.cs
│ │ ├── WatcherFixture.cs
│ │ └── XorFixture.cs
│ ├── Domain
│ │ ├── Animal.cs
│ │ ├── AnimalOwner.cs
│ │ ├── Fakers.cs
│ │ ├── Market.cs
│ │ ├── MarketPrice.cs
│ │ ├── ParentAndChildren.cs
│ │ ├── ParentChild.cs
│ │ ├── Person.cs
│ │ ├── PersonEmployment.cs
│ │ ├── PersonObs.cs
│ │ ├── PersonWithChildren.cs
│ │ ├── PersonWithEmployment.cs
│ │ ├── PersonWithFriends.cs
│ │ ├── PersonWithGender.cs
│ │ ├── PersonWithRelations.cs
│ │ ├── Pet.cs
│ │ ├── RandomPersonGenerator.cs
│ │ └── SelfObservingPerson.cs
│ ├── DynamicData.Tests.csproj
│ ├── EnumerableExFixtures.cs
│ ├── EnumerableIListFixture.cs
│ ├── Issues
│ │ ├── EmptyToChangeSetIssue.cs
│ │ ├── OnItemRemovedIssue.cs
│ │ └── Readme.txt
│ ├── Kernal
│ │ ├── CacheUpdaterFixture.cs
│ │ ├── DistinctUpdateFixture.cs
│ │ ├── EnumerableEx.cs
│ │ ├── KeyValueFixture.cs
│ │ ├── OptionFixture.cs
│ │ ├── OptionObservableFixture.cs
│ │ ├── SourceUpdaterFixture.cs
│ │ └── UpdateFixture.cs
│ ├── List
│ │ ├── AndFixture.cs
│ │ ├── AutoRefreshFixture.cs
│ │ ├── BatchFixture.cs
│ │ ├── BatchIfFixture.cs
│ │ ├── BatchIfWithTimeOutFixture.cs
│ │ ├── BufferFixture.cs
│ │ ├── BufferInitialFixture.cs
│ │ ├── CastFixture.cs
│ │ ├── ChangeAwareListFixture.cs
│ │ ├── ChangeSetFixture.cs
│ │ ├── CloneChangesFixture.cs
│ │ ├── CloneFixture.cs
│ │ ├── CreationFixtures.cs
│ │ ├── DeferUntilLoadedFixture.cs
│ │ ├── DisposeManyFixture.cs
│ │ ├── DistinctValuesFixture.cs
│ │ ├── DynamicAndFixture.cs
│ │ ├── DynamicExceptFixture.cs
│ │ ├── DynamicOrFixture.cs
│ │ ├── DynamicXOrFixture.cs
│ │ ├── EditDiffFixture.cs
│ │ ├── ExceptFixture.cs
│ │ ├── ExpireAfterFixture.cs
│ │ ├── FilterControllerFixtureWithClearAndReplace.cs
│ │ ├── FilterControllerFixtureWithDiffSet.cs
│ │ ├── FilterFixture.WithPredicateState.cs
│ │ ├── FilterFixture.cs
│ │ ├── FilterOnObservableFixture.cs
│ │ ├── FilterOnPropertyFixture.cs
│ │ ├── FilterWithObservable.cs
│ │ ├── ForEachChangeFixture.cs
│ │ ├── FromAsyncFixture.cs
│ │ ├── GroupImmutableFixture.cs
│ │ ├── GroupOnFixture.cs
│ │ ├── GroupOnPropertyFixture.cs
│ │ ├── GroupOnPropertyWithImmutableStateFixture.cs
│ │ ├── MergeChangeSetsFixture.cs
│ │ ├── MergeManyChangeSetsCacheFixture.cs
│ │ ├── MergeManyChangeSetsFixture.cs
│ │ ├── MergeManyChangeSetsListFixture.cs
│ │ ├── MergeManyFixture.cs
│ │ ├── OnItemFixture.cs
│ │ ├── OrFixture.cs
│ │ ├── PageFixture.cs
│ │ ├── QueryWhenChangedFixture.cs
│ │ ├── RecursiveTransformManyFixture.cs
│ │ ├── RefCountFixture.cs
│ │ ├── RemoveManyFixture.cs
│ │ ├── ReverseFixture.cs
│ │ ├── SelectFixture.cs
│ │ ├── SizeLimitFixture.cs
│ │ ├── SortFixture.cs
│ │ ├── SortMutableFixture.cs
│ │ ├── SortPrimitiveFixture.cs
│ │ ├── SourceListFixture.cs
│ │ ├── SourceListPreviewFixture.cs
│ │ ├── SubscribeManyFixture.cs
│ │ ├── SwitchFixture.cs
│ │ ├── ToCollectionFixture.cs
│ │ ├── ToObservableChangeSetFixture.cs
│ │ ├── TransformAsyncFixture.cs
│ │ ├── TransformFixture.cs
│ │ ├── TransformManyFixture.cs
│ │ ├── TransformManyObservableCollectionFixture.cs
│ │ ├── TransformManyProjectionFixture.cs
│ │ ├── TransformManyRefreshFixture.cs
│ │ ├── VirtualisationFixture.cs
│ │ └── XOrFixture.cs
│ ├── ObservableCollectionExFixture.cs
│ └── Utilities
│ │ ├── CacheItemRecordingObserver.cs
│ │ ├── ComparerExtensions.cs
│ │ ├── FakeScheduler.cs
│ │ ├── FakerExtensions.cs
│ │ ├── FunctionalExtensions.cs
│ │ ├── ListItemRecordingObserver.cs
│ │ ├── ObservableEx.cs
│ │ ├── ObservableExtensions.cs
│ │ ├── ObservableSpy.cs
│ │ ├── RandomizerExtensions.cs
│ │ ├── RawAnonymousObservable.cs
│ │ ├── RawAnonymousObserver.cs
│ │ ├── RecordingObserverBase.cs
│ │ ├── SelectManyExtensions.cs
│ │ ├── StressAddRemoveExtensions.cs
│ │ ├── TestSourceCache.cs
│ │ ├── TestSourceList.cs
│ │ ├── UnsynchronizedNotificationException.cs
│ │ └── ValueRecordingObserver.cs
├── DynamicData.sln
├── DynamicData
│ ├── Aggregation
│ │ ├── AggregateEnumerator.cs
│ │ ├── AggregateItem.cs
│ │ ├── AggregateType.cs
│ │ ├── AggregationEx.cs
│ │ ├── Avg.cs
│ │ ├── AvgEx.cs
│ │ ├── CountEx.cs
│ │ ├── IAggregateChangeSet.cs
│ │ ├── MaxEx.cs
│ │ ├── StdDev.cs
│ │ ├── StdDevEx.cs
│ │ └── SumEx.cs
│ ├── Alias
│ │ ├── ObservableCacheAlias.cs
│ │ └── ObservableListAlias.cs
│ ├── Attributes.cs
│ ├── Binding
│ │ ├── AbstractNotifyPropertyChanged.cs
│ │ ├── BindPaged.cs
│ │ ├── BindVirtualized.cs
│ │ ├── BindingListAdaptor.cs
│ │ ├── BindingListEventsSuspender.cs
│ │ ├── BindingListEx.cs
│ │ ├── BindingOptions.cs
│ │ ├── ExpressionBuilder.cs
│ │ ├── IEvaluateAware.cs
│ │ ├── IIndexAware.cs
│ │ ├── INotifyCollectionChangedSuspender.cs
│ │ ├── IObservableCollection.cs
│ │ ├── IObservableCollectionAdaptor.cs
│ │ ├── IObservableListEx.cs
│ │ ├── ISortedObservableCollectionAdaptor.cs
│ │ ├── NotifyPropertyChangedEx.cs
│ │ ├── Observable.cs
│ │ ├── ObservableCollectionAdaptor.cs
│ │ ├── ObservableCollectionEx.cs
│ │ ├── ObservableCollectionExtended.cs
│ │ ├── ObservablePropertyFactory.cs
│ │ ├── ObservablePropertyFactoryCache.cs
│ │ ├── ObservablePropertyPart.cs
│ │ ├── PropertyValue.cs
│ │ ├── SortAndBind.cs
│ │ ├── SortAndBindOptions.cs
│ │ ├── SortDirection.cs
│ │ ├── SortExpression.cs
│ │ ├── SortExpressionComparer.cs
│ │ ├── SortedBindingListAdaptor.cs
│ │ └── SortedObservableCollectionAdaptor.cs
│ ├── Cache
│ │ ├── CacheChangeSetEx.cs
│ │ ├── Change.cs
│ │ ├── ChangeAwareCache.cs
│ │ ├── ChangeReason.cs
│ │ ├── ChangeSet.cs
│ │ ├── DistinctChangeSet.cs
│ │ ├── GroupChangeSet.cs
│ │ ├── ICache.cs
│ │ ├── ICacheUpdater.cs
│ │ ├── IChangeSet.cs
│ │ ├── IChangeSetAdaptor.cs
│ │ ├── IConnectableCache.cs
│ │ ├── IDistinctChangeSet.cs
│ │ ├── IGroup.cs
│ │ ├── IGroupChangeSet.cs
│ │ ├── IGrouping.cs
│ │ ├── IImmutableGroupChangeSet.cs
│ │ ├── IIntermediateCache.cs
│ │ ├── IKey.cs
│ │ ├── IKeyValue.cs
│ │ ├── IKeyValueCollection.cs
│ │ ├── IObservableCache.cs
│ │ ├── IPageRequest.cs
│ │ ├── IPageResponse.cs
│ │ ├── IPagedChangeSet.cs
│ │ ├── IQuery.cs
│ │ ├── ISortedChangeSet.cs
│ │ ├── ISortedChangeSetAdaptor.cs
│ │ ├── ISourceCache.cs
│ │ ├── ISourceUpdater.cs
│ │ ├── IVirtualChangeSet.cs
│ │ ├── IVirtualRequest.cs
│ │ ├── IVirtualResponse.cs
│ │ ├── IndexedItem.cs
│ │ ├── IntermediateCache.cs
│ │ ├── Internal
│ │ │ ├── AbstractFilter.cs
│ │ │ ├── AnonymousObservableCache.cs
│ │ │ ├── AnonymousQuery.cs
│ │ │ ├── AsyncDisposeMany.cs
│ │ │ ├── AutoRefresh.cs
│ │ │ ├── BatchIf.cs
│ │ │ ├── Cache.cs
│ │ │ ├── CacheEx.cs
│ │ │ ├── CacheUpdater.cs
│ │ │ ├── Cast.cs
│ │ │ ├── ChangeSetCache.cs
│ │ │ ├── ChangeSetMergeTracker.cs
│ │ │ ├── CombineOperator.cs
│ │ │ ├── Combiner.cs
│ │ │ ├── DeferUntilLoaded.cs
│ │ │ ├── DictionaryExtensions.cs
│ │ │ ├── DisposeMany.cs
│ │ │ ├── DistinctCalculator.cs
│ │ │ ├── DynamicCombiner.cs
│ │ │ ├── DynamicFilter.cs
│ │ │ ├── DynamicGrouper.cs
│ │ │ ├── EditDiff.cs
│ │ │ ├── EditDiffChangeSet.cs
│ │ │ ├── EditDiffChangeSetOptional.cs
│ │ │ ├── ExpirableItem.cs
│ │ │ ├── ExpireAfter.ForSource.cs
│ │ │ ├── ExpireAfter.ForStream.cs
│ │ │ ├── Filter.WithPredicateState.cs
│ │ │ ├── FilterEx.cs
│ │ │ ├── FilterImmutable.cs
│ │ │ ├── FilterOnObservable.cs
│ │ │ ├── FilterOnProperty.cs
│ │ │ ├── FilteredIndexCalculator.cs
│ │ │ ├── FinallySafe.cs
│ │ │ ├── FullJoin.cs
│ │ │ ├── FullJoinMany.cs
│ │ │ ├── GroupOn.cs
│ │ │ ├── GroupOnDynamic.cs
│ │ │ ├── GroupOnImmutable.cs
│ │ │ ├── GroupOnObservable.cs
│ │ │ ├── GroupOnProperty.cs
│ │ │ ├── GroupOnPropertyWithImmutableState.cs
│ │ │ ├── IFilter.cs
│ │ │ ├── IKeySelector.cs
│ │ │ ├── ImmutableGroup.cs
│ │ │ ├── ImmutableGroupChangeSet.cs
│ │ │ ├── IndexAndNode.cs
│ │ │ ├── IndexCalculator.cs
│ │ │ ├── InnerJoin.cs
│ │ │ ├── InnerJoinMany.cs
│ │ │ ├── KeyComparer.cs
│ │ │ ├── KeySelector.cs
│ │ │ ├── KeySelectorException.cs
│ │ │ ├── KeyValueCollection.cs
│ │ │ ├── KeyValueComparer.cs
│ │ │ ├── LeftJoin.cs
│ │ │ ├── LeftJoinMany.cs
│ │ │ ├── LockFreeObservableCache.cs
│ │ │ ├── ManagedGroup.cs
│ │ │ ├── MergeChangeSets.cs
│ │ │ ├── MergeMany.cs
│ │ │ ├── MergeManyCacheChangeSets.cs
│ │ │ ├── MergeManyCacheChangeSetsSourceCompare.cs
│ │ │ ├── MergeManyItems.cs
│ │ │ ├── MergeManyListChangeSets.cs
│ │ │ ├── ObservableWithValue.cs
│ │ │ ├── OfType.cs
│ │ │ ├── OnBeingRemoved.cs
│ │ │ ├── Page.cs
│ │ │ ├── QueryWhenChanged.cs
│ │ │ ├── ReaderWriter.cs
│ │ │ ├── RefCount.cs
│ │ │ ├── RemoveKeyEnumerator.cs
│ │ │ ├── RightJoin.cs
│ │ │ ├── RightJoinMany.cs
│ │ │ ├── SizeExpirer.cs
│ │ │ ├── SizeLimiter.cs
│ │ │ ├── Sort.cs
│ │ │ ├── SortAndPage.cs
│ │ │ ├── SortAndVirtualize.cs
│ │ │ ├── SortExtensions.cs
│ │ │ ├── SortedKeyValueApplicator.cs
│ │ │ ├── SpecifiedGrouper.cs
│ │ │ ├── StaticFilter.cs
│ │ │ ├── StatusMonitor.cs
│ │ │ ├── SubscribeMany.cs
│ │ │ ├── Switch.cs
│ │ │ ├── ToObservableChangeSet.cs
│ │ │ ├── ToObservableOptional.cs
│ │ │ ├── Transform.cs
│ │ │ ├── TransformAsync.cs
│ │ │ ├── TransformImmutable.cs
│ │ │ ├── TransformMany.cs
│ │ │ ├── TransformManyAsync.cs
│ │ │ ├── TransformOnObservable.cs
│ │ │ ├── TransformWithForcedTransform.cs
│ │ │ ├── TransformWithInlineUpdate.cs
│ │ │ ├── TreeBuilder.cs
│ │ │ ├── TrueFor.cs
│ │ │ ├── UniquenessEnforcer.cs
│ │ │ └── Virtualise.cs
│ │ ├── MissingKeyException.cs
│ │ ├── Node.cs
│ │ ├── ObservableCache.cs
│ │ ├── ObservableCacheEx.SortAndBind.cs
│ │ ├── ObservableCacheEx.VirtualiseAndPage.cs
│ │ ├── ObservableCacheEx.cs
│ │ ├── PageContext.cs
│ │ ├── PageRequest.cs
│ │ ├── PageResponse.cs
│ │ ├── PagedChangeSet.cs
│ │ ├── SortAndPageOptions.cs
│ │ ├── SortAndVirtualizeOptions.cs
│ │ ├── SortOptimisations.cs
│ │ ├── SortReason.cs
│ │ ├── SortedChangeSet.cs
│ │ ├── SourceCache.cs
│ │ ├── SourceCacheEx.cs
│ │ ├── Tests
│ │ │ ├── ChangeSetAggregator.cs
│ │ │ ├── DistinctChangeSetAggregator.cs
│ │ │ ├── GroupChangeSetAggregator.cs
│ │ │ ├── PagedChangeSetAggregator.cs
│ │ │ ├── SortedChangeSetAggregator.cs
│ │ │ ├── TestEx.cs
│ │ │ └── VirtualChangeSetAggregator.cs
│ │ ├── TransformAsyncOptions.cs
│ │ ├── VirtualChangeSet.cs
│ │ ├── VirtualContext.cs
│ │ ├── VirtualRequest.cs
│ │ └── VirtualResponse.cs
│ ├── Constants.cs
│ ├── Diagnostics
│ │ ├── ChangeStatistics.cs
│ │ ├── ChangeSummary.cs
│ │ └── DiagnosticOperators.cs
│ ├── DynamicData.csproj
│ ├── DynamicData.csproj.DotSettings
│ ├── DynamicDataOptions.cs
│ ├── EnumerableEx.cs
│ ├── Experimental
│ │ ├── ExperimentalEx.cs
│ │ ├── ISubjectWithRefCount.cs
│ │ ├── IWatcher.cs
│ │ ├── SubjectWithRefCount.cs
│ │ └── Watcher.cs
│ ├── GlobalConfig.cs
│ ├── IChangeSet.cs
│ ├── Internal
│ │ ├── CacheParentSubscription.cs
│ │ ├── ExceptionMixins.cs
│ │ ├── KeyedDisposable.cs
│ │ ├── ObservableEx.cs
│ │ ├── Rxx.cs
│ │ └── SwappableLock.cs
│ ├── Kernel
│ │ ├── ConnectionStatus.cs
│ │ ├── EnumerableEx.cs
│ │ ├── EnumerableIList.cs
│ │ ├── EnumeratorIList.cs
│ │ ├── Error.cs
│ │ ├── IEnumerableIList.cs
│ │ ├── ISupportsCapacity.cs
│ │ ├── InternalEx.cs
│ │ ├── ItemWithIndex.cs
│ │ ├── ItemWithValue.cs
│ │ ├── OptionElse.cs
│ │ ├── OptionExtensions.cs
│ │ ├── OptionObservableExtensions.cs
│ │ ├── Optional.cs
│ │ ├── ParallelEx.cs
│ │ ├── ReadOnlyCollectionLight.cs
│ │ └── ReferenceEqualityComparer.cs
│ ├── List
│ │ ├── Change.cs
│ │ ├── ChangeAwareList.cs
│ │ ├── ChangeAwareListWithRefCounts.cs
│ │ ├── ChangeSet.cs
│ │ ├── ChangeSetEx.cs
│ │ ├── ChangeType.cs
│ │ ├── IChangeSet.cs
│ │ ├── IChangeSetAdaptor.cs
│ │ ├── IExtendedList.cs
│ │ ├── IGroup.cs
│ │ ├── IGrouping.cs
│ │ ├── IObservableList.cs
│ │ ├── IPageChangeSet.cs
│ │ ├── ISourceList.cs
│ │ ├── IVirtualChangeSet.cs
│ │ ├── Internal
│ │ │ ├── AnonymousObservableList.cs
│ │ │ ├── AutoRefresh.cs
│ │ │ ├── BufferIf.cs
│ │ │ ├── ChangeSetMergeTracker.cs
│ │ │ ├── ClonedListChangeSet.cs
│ │ │ ├── Combiner.cs
│ │ │ ├── DeferUntilLoaded.cs
│ │ │ ├── DisposeMany.cs
│ │ │ ├── Distinct.cs
│ │ │ ├── DynamicCombiner.cs
│ │ │ ├── EditDiff.cs
│ │ │ ├── ExpirableItem.cs
│ │ │ ├── ExpireAfter.cs
│ │ │ ├── Filter.WithPredicateState.cs
│ │ │ ├── Filter.cs
│ │ │ ├── FilterOnObservable.cs
│ │ │ ├── FilterOnProperty.cs
│ │ │ ├── FilterStatic.cs
│ │ │ ├── Group.cs
│ │ │ ├── GroupOn.cs
│ │ │ ├── GroupOnImmutable.cs
│ │ │ ├── GroupOnProperty.cs
│ │ │ ├── GroupOnPropertyWithImmutableState.cs
│ │ │ ├── ImmutableGroup.cs
│ │ │ ├── LimitSizeTo.cs
│ │ │ ├── MergeChangeSets.cs
│ │ │ ├── MergeMany.cs
│ │ │ ├── MergeManyCacheChangeSets.cs
│ │ │ ├── MergeManyListChangeSets.cs
│ │ │ ├── OnBeingAdded.cs
│ │ │ ├── OnBeingRemoved.cs
│ │ │ ├── Pager.cs
│ │ │ ├── QueryWhenChanged.cs
│ │ │ ├── ReaderWriter.cs
│ │ │ ├── RefCount.cs
│ │ │ ├── ReferenceCountTracker.cs
│ │ │ ├── Sort.cs
│ │ │ ├── SubscribeMany.cs
│ │ │ ├── Switch.cs
│ │ │ ├── ToObservableChangeSet.cs
│ │ │ ├── TransformAsync.cs
│ │ │ ├── TransformMany.cs
│ │ │ ├── Transformer.cs
│ │ │ ├── UnifiedChange.cs
│ │ │ └── Virtualiser.cs
│ │ ├── ItemChange.cs
│ │ ├── Linq
│ │ │ ├── AddKeyEnumerator.cs
│ │ │ ├── ItemChangeEnumerator.cs
│ │ │ ├── Reverser.cs
│ │ │ ├── UnifiedChangeEnumerator.cs
│ │ │ └── WithoutIndexEnumerator.cs
│ │ ├── ListChangeReason.cs
│ │ ├── ListEx.cs
│ │ ├── ListFilterPolicy.cs
│ │ ├── ObservableListEx.cs
│ │ ├── PageChangeSet.cs
│ │ ├── RangeChange.cs
│ │ ├── SortException.cs
│ │ ├── SortOptions.cs
│ │ ├── SourceList.cs
│ │ ├── SourceListEditConvenienceEx.cs
│ │ ├── SourceListEx.cs
│ │ ├── Tests
│ │ │ ├── ChangeSetAggregator.cs
│ │ │ └── ListTextEx.cs
│ │ ├── UnspecifiedIndexException.cs
│ │ └── VirtualChangeSet.cs
│ ├── ObservableChangeSet.cs
│ ├── ObsoleteEx.cs
│ ├── Platforms
│ │ └── net45
│ │ │ ├── PFilter.cs
│ │ │ ├── PSubscribeMany.cs
│ │ │ ├── PTransform.cs
│ │ │ ├── ParallelEx.cs
│ │ │ ├── ParallelOperators.cs
│ │ │ ├── ParallelType.cs
│ │ │ └── ParallelisationOptions.cs
│ └── Polyfills
│ │ ├── CompilerFeatureRequiredAttribute.cs
│ │ ├── DynamicallyAccessedMembersAttribute.cs
│ │ ├── EnumEx.cs
│ │ ├── IsExternalInit.cs
│ │ ├── ListEnsureCapacity.cs
│ │ └── RequiredMemberAttribute.cs
├── coverlet.xml
├── global.json
└── stylecop.json
└── version.json
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Questions
4 | url: https://github.com/reactivemarbles/DynamicData/discussions
5 | about: 'For general questions about DynamicData, ask in the GitHub discussions'
6 | - name: Chat
7 | url: https://www.reactiveui.net/slack
8 | about: 'Our slack chat community invite'
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Create a request for functionality enhancement
3 | title: "[Feature]: "
4 | labels: ["feature"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | ## Please note although we can't commit to any timeline
10 | - type: textarea
11 | id: description
12 | attributes:
13 | label: Describe the functionality desired 🐞
14 | description: A clear and concise description of what the feature request is.
15 | value: "A feature request"
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: functionality-steps
20 | attributes:
21 | label: The steps the functionality will provide
22 | description: "Steps of the desired functionality:"
23 | value: |
24 | 1. Go to '...'
25 | 2. Click on '....'
26 | 3. Scroll down to '....'
27 | 4. See error
28 | validations:
29 | required: true
30 | - type: input
31 | id: considerations
32 | attributes:
33 | label: Considerations
34 | description: "What attempts have you attempted with the existing functionality?"
35 | validations:
36 | required: true
37 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "local>reactivemarbles/.github:renovate"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.github/workflows/ci-build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | env:
8 | configuration: Release
9 | productNamespacePrefix: "DynamicData"
10 |
11 | jobs:
12 | build:
13 | runs-on: windows-2022
14 | outputs:
15 | nbgv: ${{ steps.nbgv.outputs.SemVer2 }}
16 | steps:
17 |
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 | lfs: true
23 |
24 | - name: Setup .NET 6/7/8/9
25 | uses: actions/setup-dotnet@v4
26 | with:
27 | dotnet-version: |
28 | 6.0.x
29 | 7.0.x
30 | 8.0.x
31 | 9.0.x
32 |
33 | - name: NBGV
34 | id: nbgv
35 | uses: dotnet/nbgv@master
36 | with:
37 | setAllVars: true
38 |
39 | - name: NuGet Restore
40 | run: dotnet restore DynamicData.sln
41 | working-directory: src
42 |
43 | - name: Build
44 | run: dotnet build --no-restore --configuration Release DynamicData.sln
45 | working-directory: src
46 |
47 | - name: Run Tests
48 | run: dotnet test --no-restore --configuration Release DynamicData.sln
49 | working-directory: src
50 |
51 | - name: Pack
52 | run: dotnet pack --no-restore --configuration Release DynamicData.sln
53 | working-directory: src
54 |
55 | - name: Create NuGet Artifacts
56 | uses: actions/upload-artifact@master
57 | with:
58 | name: nuget
59 | path: '**/*.nupkg'
60 |
--------------------------------------------------------------------------------
/.github/workflows/lock.yml:
--------------------------------------------------------------------------------
1 | name: 'Lock Threads'
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 | workflow_dispatch:
7 |
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 |
12 | concurrency:
13 | group: lock
14 |
15 | jobs:
16 | action:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: dessant/lock-threads@v5
20 | with:
21 | github-token: ${{ github.token }}
22 | issue-inactive-days: '14'
23 | pr-inactive-days: '14'
24 | issue-comment: >
25 | This issue has been automatically locked since there
26 | has not been any recent activity after it was closed.
27 | Please open a new issue for related bugs.
28 | pr-comment: >
29 | This pull request has been automatically locked since there
30 | has not been any recent activity after it was closed.
31 | Please open a new issue for related bugs.
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Roland Pheasant 2011-2022
4 |
5 | All rights reserved.
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/NonProduction/DynamicData.Profile/Allocations.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2019 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System;
6 |
7 | namespace DynamicData.Profile
8 | {
9 | public static class Allocations
10 | {
11 | public static AllocationsCount Run(Action action)
12 | {
13 | var initial = GC.GetAllocatedBytesForCurrentThread();
14 | action();
15 | var final = GC.GetAllocatedBytesForCurrentThread();
16 |
17 | return new AllocationsCount(initial, final);
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/NonProduction/DynamicData.Profile/AllocationsCount.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2019 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Profile
6 | {
7 | public class AllocationsCount
8 | {
9 |
10 | public long InitialSize { get; }
11 | public long FinalSize { get; }
12 | public long Size => FinalSize - InitialSize;
13 |
14 | /// Initializes a new instance of the class.
15 | public AllocationsCount(long initialSize, long finalSize)
16 | {
17 | InitialSize = initialSize;
18 | FinalSize = finalSize;
19 | }
20 |
21 | public override string ToString()
22 | {
23 | return $"Allocation Bytes: {Size:N0}";
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/NonProduction/DynamicData.Profile/DynamicData.Profile.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | $(NoWarn);CS0618
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/NonProduction/DynamicData.Profile/Person.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2019 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Profile
6 | {
7 | public class Person
8 | {
9 | public string Name { get; }
10 | public int Age { get; }
11 |
12 | public Person(string name, int age)
13 | {
14 | Name = name;
15 | Age = age;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivemarbles/DynamicData/5f9a27c7e9830de4d0d325be1a5f228a48693fd5/images/logo.png
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/Cache/SourceCache.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2019 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.Linq;
6 |
7 | using BenchmarkDotNet.Attributes;
8 | using BenchmarkDotNet.Jobs;
9 |
10 | namespace DynamicData.Benchmarks.Cache
11 | {
12 | public class BenchmarkItem
13 | {
14 | public int Id { get; }
15 |
16 | public BenchmarkItem(int id)
17 | {
18 | Id = id;
19 | }
20 | }
21 |
22 | [SimpleJob(RuntimeMoniker.NetCoreApp31)]
23 | [MemoryDiagnoser]
24 | [MarkdownExporterAttribute.GitHub]
25 | public class SourceCache
26 | {
27 | private SourceCache? _cache;
28 | private BenchmarkItem[] _items = Enumerable.Range(1, 100).Select(i => new BenchmarkItem(i)).ToArray();
29 |
30 | [GlobalSetup]
31 | public void Setup()
32 | {
33 | _cache = new SourceCache(i => i.Id);
34 | }
35 |
36 | [Params(1, 100, 1_000, 10_000, 100_000)]
37 | public int N;
38 |
39 | [IterationSetup]
40 | public void SetupIteration()
41 | {
42 | _cache?.Clear();
43 | _items = Enumerable.Range(1, N).Select(i => new BenchmarkItem(i)).ToArray();
44 | }
45 |
46 | [GlobalCleanup]
47 | public void Teardown()
48 | {
49 | _cache?.Dispose();
50 | _cache = null;
51 | }
52 |
53 | [Benchmark]
54 | public void Add() => _cache?.AddOrUpdate(_items);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/DynamicData.Benchmarks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0-windows
6 | AnyCPU
7 | false
8 | ;1591;1701;1702;1705;CA1822;CA1001
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/List/DisposeMany_List.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2019 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System;
6 | using System.Reactive.Disposables;
7 |
8 | using BenchmarkDotNet.Attributes;
9 |
10 | namespace DynamicData.Benchmarks.List
11 | {
12 | [MemoryDiagnoser]
13 | [MarkdownExporterAttribute.GitHub]
14 | public class DisposeMany_List
15 | {
16 | [Benchmark]
17 | [Arguments(1, 0)]
18 | [Arguments(1, 1)]
19 | [Arguments(10, 0)]
20 | [Arguments(10, 1)]
21 | [Arguments(10, 10)]
22 | [Arguments(100, 0)]
23 | [Arguments(100, 1)]
24 | [Arguments(100, 10)]
25 | [Arguments(100, 100)]
26 | [Arguments(1_000, 0)]
27 | [Arguments(1_000, 1)]
28 | [Arguments(1_000, 10)]
29 | [Arguments(1_000, 100)]
30 | [Arguments(1_000, 1_000)]
31 | public void AddsRemovesAndFinalization(int addCount, int removeCount)
32 | {
33 | using var source = new SourceList();
34 |
35 | using var subscription = source
36 | .Connect()
37 | .DisposeMany()
38 | .Subscribe();
39 |
40 | for(var i = 0; i < addCount; ++i)
41 | source.Add(Disposable.Create(static () => { }));
42 |
43 | while(source.Count > (addCount - removeCount))
44 | source.RemoveAt(source.Count - 1);
45 |
46 | subscription.Dispose();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/List/SourceList.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2019 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.Linq;
6 |
7 | using BenchmarkDotNet.Attributes;
8 | using BenchmarkDotNet.Jobs;
9 |
10 | namespace DynamicData.Benchmarks.List
11 | {
12 | [SimpleJob(RuntimeMoniker.NetCoreApp31)]
13 | [MemoryDiagnoser]
14 | [MarkdownExporterAttribute.GitHub]
15 | public class SourceList
16 | {
17 | private SourceList? _sourceList;
18 | private string[]? _items;
19 |
20 | [Params(1, 100, 1_000, 10_000, 100_000)]
21 | public int N;
22 |
23 | [GlobalSetup]
24 | public void Setup() => _sourceList = new SourceList();
25 |
26 | [IterationSetup]
27 | public void SetupIteration()
28 | {
29 | _sourceList?.Clear();
30 | _items = Enumerable.Range(1, N).Select(i => i.ToString()).ToArray();
31 | }
32 |
33 | [GlobalCleanup]
34 | public void Teardown() => _sourceList = null;
35 |
36 | [Benchmark]
37 | public void AddRange() => _sourceList?.AddRange(_items!);
38 |
39 | [Benchmark]
40 | public void Insert() => _sourceList?.InsertRange(_items!, 0);
41 | }
42 | }
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/List/ToObservableChangeSet_List.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Subjects;
3 |
4 | using BenchmarkDotNet.Attributes;
5 | using BenchmarkDotNet.Columns;
6 |
7 | namespace DynamicData.Benchmarks.List;
8 |
9 | [MemoryDiagnoser]
10 | [MarkdownExporterAttribute.GitHub]
11 | [HideColumns(Column.Ratio, Column.RatioSD, Column.AllocRatio)]
12 | public class ToObservableChangeSet_List
13 | {
14 | [Benchmark]
15 | [Arguments(0, -1)]
16 | [Arguments(0, 0)]
17 | [Arguments(0, 1)]
18 | [Arguments(1, 1)]
19 | [Arguments(10, -1)]
20 | [Arguments(10, 1)]
21 | [Arguments(10, 5)]
22 | [Arguments(10, 10)]
23 | [Arguments(100, -1)]
24 | [Arguments(100, 10)]
25 | [Arguments(100, 50)]
26 | [Arguments(100, 100)]
27 | [Arguments(1_000, -1)]
28 | [Arguments(1_000, 100)]
29 | [Arguments(1_000, 500)]
30 | [Arguments(1_000, 1_000)]
31 | public void AddsUpdatesAndFinalization(int itemCount, int sizeLimit)
32 | {
33 | using var source = new Subject();
34 |
35 | using var subscription = source
36 | .ToObservableChangeSet(limitSizeTo: sizeLimit)
37 | .Subscribe();
38 |
39 | var indexModulus = (itemCount / 2) + 1;
40 | for(var i = 0; i < itemCount; ++i)
41 | source.OnNext(i);
42 | source.OnCompleted();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/Polyfills/CompilerFeatureRequiredAttribute.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace System.Runtime.CompilerServices;
6 |
7 | // Allows use of the C#11 `required` keyword, internally within this library, when targeting frameworks older than .NET 7.
8 | [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
9 | internal sealed class CompilerFeatureRequiredAttribute(string featureName)
10 | : Attribute
11 | {
12 | public const string RefStructs
13 | = nameof(RefStructs);
14 |
15 | public const string RequiredMembers
16 | = nameof(RequiredMembers);
17 |
18 | public string FeatureName { get; } = featureName;
19 |
20 | public bool IsOptional { get; init; }
21 | }
22 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/Polyfills/IsExternalInit.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace System.Runtime.CompilerServices;
6 |
7 | // Allows use of the C#11 `init` keyword, internally within this library, when targeting frameworks older than .NET 5.
8 | internal sealed class IsExternalInit
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/Polyfills/RequiredMemberAttribute.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.ComponentModel;
6 |
7 | namespace System.Runtime.CompilerServices;
8 |
9 | // Allows use of the C#11 `required` keyword, internally within this library, when targeting frameworks older than .NET 7.
10 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
11 | [EditorBrowsable(EditorBrowsableState.Never)]
12 | internal sealed class RequiredMemberAttribute
13 | : Attribute
14 | {
15 | }
16 |
--------------------------------------------------------------------------------
/src/DynamicData.Benchmarks/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2019 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.IO;
6 | using System.Runtime.CompilerServices;
7 |
8 | using BenchmarkDotNet.Configs;
9 | using BenchmarkDotNet.Running;
10 |
11 | namespace DynamicData.Benchmarks
12 | {
13 | public static class Program
14 | {
15 | public static void Main(string[] args)
16 | => BenchmarkSwitcher
17 | .FromAssembly(typeof(Program).Assembly)
18 | .Run(args, DefaultConfig.Instance
19 | .WithArtifactsPath(Path.Combine(
20 | GetProjectRootDirectory(),
21 | Path.GetFileName(DefaultConfig.Instance.ArtifactsPath))));
22 |
23 | private static string GetProjectRootDirectory([CallerFilePath] string? callerFilePath = null)
24 | => Path.GetDirectoryName(callerFilePath)!;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/DynamicData.Tests/API/ApiApprovalTests.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using System.Threading.Tasks;
3 | using Xunit;
4 |
5 | namespace DynamicData.APITests
6 | {
7 | ///
8 | /// Tests for handling API approval.
9 | ///
10 | [ExcludeFromCodeCoverage]
11 | public class ApiApprovalTests
12 | {
13 | ///
14 | /// Tests to make sure the API of DynamicData project is approved.
15 | ///
16 | [Fact]
17 | public Task DynamicDataTests() => typeof(VirtualRequest).Assembly.CheckApproval(["DynamicData"]);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/API/ApiExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using System.Reflection;
4 | using System.Runtime.CompilerServices;
5 | using System.Threading.Tasks;
6 | using PublicApiGenerator;
7 | using VerifyXunit;
8 |
9 | namespace DynamicData.APITests;
10 |
11 | ///
12 | /// A helper for doing API approvals.
13 | ///
14 | [ExcludeFromCodeCoverage]
15 | public static class ApiExtensions
16 | {
17 | ///
18 | /// Checks to make sure the API is approved.
19 | ///
20 | /// The assembly that is being checked.
21 | /// The namespaces.
22 | /// The caller file path.
23 | ///
24 | /// A Task.
25 | ///
26 | public static async Task CheckApproval(this Assembly assembly, string[] namespaces, [CallerFilePath] string filePath = "")
27 | {
28 | var generatorOptions = new ApiGeneratorOptions { AllowNamespacePrefixes = namespaces };
29 | var apiText = assembly.GeneratePublicApi(generatorOptions);
30 | var result = await Verifier.Verify(apiText, null, filePath)
31 | .UniqueForRuntimeAndVersion()
32 | .ScrubEmptyLines()
33 | .ScrubLines(l =>
34 | l.StartsWith("[assembly: AssemblyVersion(", StringComparison.InvariantCulture) ||
35 | l.StartsWith("[assembly: AssemblyFileVersion(", StringComparison.InvariantCulture) ||
36 | l.StartsWith("[assembly: AssemblyInformationalVersion(", StringComparison.InvariantCulture) ||
37 | l.StartsWith("[assembly: System.Reflection.AssemblyMetadata(", StringComparison.InvariantCulture));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Cache/BatchFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using DynamicData.Tests.Domain;
4 |
5 | using FluentAssertions;
6 |
7 | using Microsoft.Reactive.Testing;
8 |
9 | using Xunit;
10 |
11 | namespace DynamicData.Tests.Cache;
12 |
13 | public class BatchFixture : IDisposable
14 | {
15 | private readonly ChangeSetAggregator _results;
16 |
17 | private readonly TestScheduler _scheduler;
18 |
19 | private readonly ISourceCache _source;
20 |
21 | public BatchFixture()
22 | {
23 | _scheduler = new TestScheduler();
24 | _source = new SourceCache(p => p.Key);
25 | _results = _source.Connect().Batch(TimeSpan.FromMinutes(1), _scheduler).AsAggregator();
26 | }
27 |
28 | public void Dispose()
29 | {
30 | _results.Dispose();
31 | _source.Dispose();
32 | }
33 |
34 | [Fact]
35 | public void NoResultsWillBeReceivedBeforeClosingBuffer()
36 | {
37 | _source.AddOrUpdate(new Person("A", 1));
38 | _results.Messages.Count.Should().Be(0, "There should be no messages");
39 | }
40 |
41 | [Fact]
42 | public void ResultsWillBeReceivedAfterClosingBuffer()
43 | {
44 | _source.AddOrUpdate(new Person("A", 1));
45 |
46 | //go forward an arbitary amount of time
47 | _scheduler.AdvanceBy(TimeSpan.FromSeconds(61).Ticks);
48 | _results.Messages.Count.Should().Be(1, "Should be 1 update");
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Cache/BufferInitialFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using DynamicData.Tests.Domain;
6 |
7 | using FluentAssertions;
8 |
9 | using Microsoft.Reactive.Testing;
10 |
11 | using Xunit;
12 |
13 | namespace DynamicData.Tests.Cache;
14 |
15 | public class BufferInitialFixture
16 | {
17 | private static readonly ICollection People = Enumerable.Range(1, 10_000).Select(i => new Person(i.ToString(), i)).ToList();
18 |
19 | [Fact]
20 | public void BufferInitial()
21 | {
22 | var scheduler = new TestScheduler();
23 |
24 | using var cache = new SourceCache(i => i.Name);
25 | using var aggregator = cache.Connect().BufferInitial(TimeSpan.FromSeconds(1), scheduler).AsAggregator();
26 | foreach (var item in People)
27 | {
28 | cache.AddOrUpdate(item);
29 | }
30 |
31 | aggregator.Data.Count.Should().Be(0);
32 | aggregator.Messages.Count.Should().Be(0);
33 |
34 | scheduler.Start();
35 |
36 | aggregator.Data.Count.Should().Be(10_000);
37 | aggregator.Messages.Count.Should().Be(1);
38 |
39 | cache.AddOrUpdate(new Person("_New", 1));
40 |
41 | aggregator.Data.Count.Should().Be(10_001);
42 | aggregator.Messages.Count.Should().Be(2);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Cache/ExpireAfterFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Concurrency;
3 |
4 | using Microsoft.Reactive.Testing;
5 |
6 | namespace DynamicData.Tests.Cache;
7 |
8 | public static partial class ExpireAfterFixture
9 | {
10 | private static TestScheduler CreateTestScheduler()
11 | {
12 | var scheduler = new TestScheduler();
13 | scheduler.AdvanceTo(DateTimeOffset.FromUnixTimeMilliseconds(0).Ticks);
14 |
15 | return scheduler;
16 | }
17 |
18 | private static Func CreateTimeSelector(IScheduler scheduler)
19 | => item => item.Expiration - scheduler.Now;
20 |
21 | private sealed class TestItem
22 | {
23 | public required int Id { get; init; }
24 |
25 | public DateTimeOffset? Expiration { get; set; }
26 | }
27 |
28 | private sealed record StressItem
29 | {
30 | public required int Id { get; init; }
31 |
32 | public required TimeSpan? Lifetime { get; init; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Cache/FilterOnConnectFixture.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace DynamicData.Tests.Cache;
6 |
7 | ///
8 | /// See https://github.com/reactivemarbles/DynamicData/issues/400
9 | ///
10 | public class FilterOnConnectFixture
11 | {
12 | [Fact]
13 | public void ClearingSourceCacheWithPredicateShouldClearTheData()
14 | {
15 | // having
16 | var source = new SourceCache(it => it);
17 | source.AddOrUpdate(1);
18 | var results = source.Connect(it => true).AsAggregator();
19 |
20 | // when
21 | source.Clear();
22 |
23 | // then
24 | results.Data.Count.Should().Be(0, "Should be 0");
25 | }
26 |
27 | [Fact]
28 | public void UpdatesExistedBeforeConnectWithoutPredicateShouldBeVisibleAsPreviousWhenNewUpdatesTriggered()
29 | {
30 | // having
31 | var source = new SourceCache(it => it);
32 | source.AddOrUpdate(1);
33 | var results = source.Connect().AsAggregator();
34 |
35 | // when
36 | source.AddOrUpdate(1);
37 |
38 | // then
39 | results.Messages.Count.Should().Be(2, "Should be 2 updates");
40 | results.Messages[1].First().Previous.HasValue.Should().Be(true, "Should have previous value");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Cache/ForEachChangeFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using DynamicData.Tests.Domain;
5 |
6 | using FluentAssertions;
7 |
8 | using Xunit;
9 |
10 | namespace DynamicData.Tests.Cache;
11 |
12 | public class ForEachChangeFixture : IDisposable
13 | {
14 | private readonly ISourceCache _source;
15 |
16 | public ForEachChangeFixture() => _source = new SourceCache(p => p.Name);
17 |
18 | public void Dispose() => _source.Dispose();
19 |
20 | [Fact]
21 | public void Test()
22 | {
23 | var messages = new List>();
24 | var messageWriter = _source.Connect().ForEachChange(messages.Add).Subscribe();
25 |
26 | _source.AddOrUpdate(new RandomPersonGenerator().Take(100));
27 | messageWriter.Dispose();
28 |
29 | messages.Count.Should().Be(100);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Cache/IgnoreUpdateFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using DynamicData.Tests.Domain;
4 |
5 | using FluentAssertions;
6 |
7 | using Xunit;
8 |
9 | namespace DynamicData.Tests.Cache;
10 |
11 | public class IgnoreUpdateFixture : IDisposable
12 | {
13 | private readonly ChangeSetAggregator _results;
14 |
15 | private readonly ISourceCache _source;
16 |
17 | public IgnoreUpdateFixture()
18 | {
19 | _source = new SourceCache(p => p.Key);
20 | _results = new ChangeSetAggregator(_source.Connect().IgnoreUpdateWhen((current, previous) => current == previous));
21 | }
22 |
23 | public void Dispose()
24 | {
25 | _source.Dispose();
26 | _results.Dispose();
27 | }
28 |
29 | [Fact]
30 | public void IgnoreFunctionWillIgnoreSubsequentUpdatesOfAnItem()
31 | {
32 | var person = new Person("Person", 10);
33 | _source.AddOrUpdate(person);
34 | _source.AddOrUpdate(person);
35 | _source.AddOrUpdate(person);
36 |
37 | _results.Messages.Count.Should().Be(1, "Should be 1 updates");
38 | _results.Data.Count.Should().Be(1, "Should be 1 item in the cache");
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Cache/IncludeUpdateFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using DynamicData.Tests.Domain;
4 |
5 | using FluentAssertions;
6 |
7 | using Xunit;
8 |
9 | namespace DynamicData.Tests.Cache;
10 |
11 | public class IncludeUpdateFixture : IDisposable
12 | {
13 | private readonly ChangeSetAggregator _results;
14 |
15 | private readonly ISourceCache _source;
16 |
17 | public IncludeUpdateFixture()
18 | {
19 | _source = new SourceCache(p => p.Key);
20 | _results = new ChangeSetAggregator(_source.Connect().IncludeUpdateWhen((current, previous) => current != previous));
21 | }
22 |
23 | public void Dispose()
24 | {
25 | _source.Dispose();
26 | _results.Dispose();
27 | }
28 |
29 | [Fact]
30 | public void IgnoreFunctionWillIgnoreSubsequentUpdatesOfAnItem()
31 | {
32 | var person = new Person("Person", 10);
33 | _source.AddOrUpdate(person);
34 | _source.AddOrUpdate(person);
35 | _source.AddOrUpdate(person);
36 |
37 | _results.Messages.Count.Should().Be(1, "Should be 1 updates");
38 | _results.Data.Count.Should().Be(1, "Should be 1 item in the cache");
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Cache/KeyValueCollectionEx.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace DynamicData.Tests.Cache;
5 |
6 | public static class KeyValueCollectionEx
7 | {
8 | public static IDictionary> Indexed(this IKeyValueCollection source)
9 | where TKey : notnull => source.Select((kv, idx) => new IndexedItem(kv.Value, kv.Key, idx)).ToDictionary(i => i.Key);
10 | }
11 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Domain/ParentChild.cs:
--------------------------------------------------------------------------------
1 | namespace DynamicData.Tests.Domain;
2 |
3 | public class ParentChild(Person child, Person parent)
4 | {
5 | public Person Child { get; } = child;
6 |
7 | public Person Parent { get; } = parent;
8 | }
9 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Domain/PersonWithChildren.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace DynamicData.Tests.Domain;
5 |
6 | public class PersonWithChildren : IKey
7 | {
8 | public PersonWithChildren(string name, int age)
9 | : this(name, age, Enumerable.Empty())
10 | {
11 | }
12 |
13 | public PersonWithChildren(string name, int age, IEnumerable relations)
14 | {
15 | Name = name;
16 | Age = age;
17 | KeyValue = Name;
18 | Relations = relations;
19 | Key = name;
20 | }
21 |
22 | public int Age { get; set; }
23 |
24 | ///
25 | /// The key
26 | ///
27 | public string Key { get; }
28 |
29 | public string KeyValue { get; }
30 |
31 | public string Name { get; }
32 |
33 | public IEnumerable Relations { get; }
34 |
35 | public override string ToString() => $"{Name}. {Age}";
36 | }
37 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Domain/PersonWithEmployment.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DynamicData.Tests.Domain;
4 |
5 | public class PersonWithEmployment(IGroup source) : IDisposable
6 | {
7 | private readonly IGroup _source = source;
8 |
9 | public int EmploymentCount => EmploymentData.Count;
10 |
11 | public IObservableCache EmploymentData { get; } = source?.Cache!;
12 |
13 | public string Person => _source.Key;
14 |
15 | public void Dispose() => EmploymentData.Dispose();
16 |
17 | public override string ToString() => $"Person: {Person}. Count {EmploymentCount}";
18 | }
19 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Domain/PersonWithFriends.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | using DynamicData.Binding;
5 |
6 | namespace DynamicData.Tests.Domain;
7 |
8 | public class PersonWithFriends(string name, int age, IEnumerable friends) : AbstractNotifyPropertyChanged, IKey
9 | {
10 | private int _age = age;
11 |
12 | private IEnumerable _friends = friends;
13 |
14 | public PersonWithFriends(string name, int age)
15 | : this(name, age, Enumerable.Empty())
16 | {
17 | }
18 |
19 | public int Age
20 | {
21 | get => _age;
22 | set => SetAndRaise(ref _age, value);
23 | }
24 |
25 | public IEnumerable Friends
26 | {
27 | get => _friends;
28 | set => SetAndRaise(ref _friends, value);
29 | }
30 |
31 | ///
32 | /// The key
33 | ///
34 | public string Key { get; } = name;
35 |
36 | public string Name { get; } = name;
37 |
38 | public override string ToString() => $"{Name}. {Age}";
39 | }
40 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Domain/PersonWithRelations.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace DynamicData.Tests.Domain;
5 |
6 | public class PersonWithRelations : IKey
7 | {
8 | public PersonWithRelations(string name, int age)
9 | : this(name, age, Enumerable.Empty())
10 | {
11 | }
12 |
13 | public PersonWithRelations(string name, int age, IEnumerable relations)
14 | {
15 | Name = name;
16 | Age = age;
17 | KeyValue = Name;
18 | Relations = relations;
19 | Key = name;
20 | Pet = Enumerable.Empty();
21 | }
22 |
23 | public int Age { get; }
24 |
25 | ///
26 | /// The key
27 | ///
28 | public string Key { get; }
29 |
30 | public string KeyValue { get; }
31 |
32 | public string Name { get; }
33 |
34 | public IEnumerable Pet { get; set; }
35 |
36 | public IEnumerable Relations { get; }
37 |
38 | public override string ToString() => $"{Name}. {Age}";
39 | }
40 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Domain/Pet.cs:
--------------------------------------------------------------------------------
1 | namespace DynamicData.Tests.Domain;
2 |
3 | public class Pet
4 | {
5 | public string? Animal { get; set; }
6 |
7 | public string? Name { get; set; }
8 | }
9 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Domain/SelfObservingPerson.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Linq;
3 |
4 | namespace DynamicData.Tests.Domain;
5 |
6 | public class SelfObservingPerson : IDisposable
7 | {
8 | private readonly IDisposable _cleanUp;
9 |
10 | public SelfObservingPerson(IObservable observable) => _cleanUp = observable.Finally(() => Completed = true).Subscribe(
11 | p =>
12 | {
13 | Person = p;
14 | UpdateCount++;
15 | });
16 |
17 | public bool Completed { get; private set; }
18 |
19 | public Person? Person { get; private set; }
20 |
21 | public int UpdateCount { get; private set; }
22 |
23 | ///
24 | ///put here the code to dispose all managed and unmanaged resources
25 | ///
26 | public void Dispose() => _cleanUp.Dispose();
27 | }
28 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/DynamicData.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | $(NoWarn);CS0618;CA1801;CA1812;CA1816;CA1062;CA1063;CS8767;CS8602;CS8618;IDE1006
5 | 2.*
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | all
24 | runtime; build; native; contentfiles; analyzers
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Issues/EmptyToChangeSetIssue.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using System.Reactive;
3 | using DynamicData.Binding;
4 | using FluentAssertions;
5 | using Xunit;
6 |
7 | namespace DynamicData.Tests.Issues
8 | {
9 | public class EmptyToChangeSetIssue
10 | {
11 | [Fact]
12 | public void EmptyCollectionToChangeSetBehaviour()
13 | {
14 | var collection = new ObservableCollection();
15 |
16 | var results = collection.ToObservableChangeSet().AsAggregator();
17 | results.Messages.Count.Should()
18 | .BeGreaterThan(0, "An empty collection should still have an update, even if empty.");
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Issues/Readme.txt:
--------------------------------------------------------------------------------
1 | This is a dumping ground for issue replication
2 |
3 | I am happy for these to be checked in until tests are moved to a more appropriate place.
4 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Kernal/KeyValueFixture.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | using DynamicData.Tests.Domain;
4 |
5 | using FluentAssertions;
6 |
7 | using Xunit;
8 |
9 | namespace DynamicData.Tests.Kernal;
10 |
11 | public class KeyValueFixture
12 | {
13 | [Fact]
14 | public void Create()
15 | {
16 | var person = new Person("Person", 10);
17 | var kv = new KeyValuePair("Person", person);
18 |
19 | kv.Key.Should().Be("Person");
20 | kv.Value.Should().Be(person);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/BatchFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Linq;
3 |
4 | using DynamicData.Tests.Domain;
5 |
6 | using FluentAssertions;
7 |
8 | using Microsoft.Reactive.Testing;
9 |
10 | using Xunit;
11 |
12 | namespace DynamicData.Tests.List;
13 |
14 | public class BatchFixture : IDisposable
15 | {
16 | private readonly ChangeSetAggregator _results;
17 |
18 | private readonly TestScheduler _scheduler;
19 |
20 | private readonly ISourceList _source;
21 |
22 | public BatchFixture()
23 | {
24 | _scheduler = new TestScheduler();
25 | _source = new SourceList();
26 | _results = _source.Connect().Buffer(TimeSpan.FromMinutes(1), _scheduler).FlattenBufferResult().AsAggregator();
27 | }
28 |
29 | public void Dispose()
30 | {
31 | _results.Dispose();
32 | _source.Dispose();
33 | }
34 |
35 | [Fact]
36 | public void NoResultsWillBeReceivedBeforeClosingBuffer()
37 | {
38 | _source.Add(new Person("A", 1));
39 | _results.Messages.Count.Should().Be(0, "There should be no messages");
40 | }
41 |
42 | [Fact]
43 | public void ResultsWillBeReceivedAfterClosingBuffer()
44 | {
45 | _source.Add(new Person("A", 1));
46 |
47 | //go forward an arbitary amount of time
48 | _scheduler.AdvanceBy(TimeSpan.FromSeconds(61).Ticks);
49 | _results.Messages.Count.Should().Be(1, "Should be 1 update");
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/BufferFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Linq;
3 |
4 | using DynamicData.Tests.Domain;
5 |
6 | using FluentAssertions;
7 |
8 | using Microsoft.Reactive.Testing;
9 |
10 | using Xunit;
11 |
12 | namespace DynamicData.Tests.List;
13 |
14 | public class BufferFixture : IDisposable
15 | {
16 | private readonly ChangeSetAggregator _results;
17 |
18 | private readonly TestScheduler _scheduler;
19 |
20 | private readonly ISourceList _source;
21 |
22 | public BufferFixture()
23 | {
24 | _scheduler = new TestScheduler();
25 | _source = new SourceList();
26 | _results = _source.Connect().Buffer(TimeSpan.FromMinutes(1), _scheduler).FlattenBufferResult().AsAggregator();
27 | }
28 |
29 | public void Dispose()
30 | {
31 | _results.Dispose();
32 | _source.Dispose();
33 | }
34 |
35 | [Fact]
36 | public void NoResultsWillBeReceivedBeforeClosingBuffer()
37 | {
38 | _source.Add(new Person("A", 1));
39 | _results.Messages.Count.Should().Be(0, "There should be no messages");
40 | }
41 |
42 | [Fact]
43 | public void ResultsWillBeReceivedAfterClosingBuffer()
44 | {
45 | _source.Add(new Person("A", 1));
46 |
47 | //go forward an arbitary amount of time
48 | _scheduler.AdvanceBy(TimeSpan.FromSeconds(61).Ticks);
49 | _results.Messages.Count.Should().Be(1, "Should be 1 update");
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/BufferInitialFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using DynamicData.Tests.Domain;
6 |
7 | using FluentAssertions;
8 |
9 | using Microsoft.Reactive.Testing;
10 |
11 | using Xunit;
12 |
13 | namespace DynamicData.Tests.List;
14 |
15 | public class BufferInitialFixture
16 | {
17 | private static readonly ICollection People = Enumerable.Range(1, 10_000).Select(i => new Person(i.ToString(), i)).ToList();
18 |
19 | [Fact]
20 | public void BufferInitial()
21 | {
22 | var scheduler = new TestScheduler();
23 |
24 | using var cache = new SourceList();
25 | using var aggregator = cache.Connect().BufferInitial(TimeSpan.FromSeconds(1), scheduler).AsAggregator();
26 | foreach (var item in People)
27 | {
28 | cache.Add(item);
29 | }
30 |
31 | aggregator.Data.Count.Should().Be(0);
32 | aggregator.Messages.Count.Should().Be(0);
33 |
34 | scheduler.Start();
35 |
36 | aggregator.Data.Count.Should().Be(10_000);
37 | aggregator.Messages.Count.Should().Be(1);
38 |
39 | cache.Add(new Person("_New", 1));
40 |
41 | aggregator.Data.Count.Should().Be(10_001);
42 | aggregator.Messages.Count.Should().Be(2);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/CastFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | using FluentAssertions;
5 |
6 | using Xunit;
7 |
8 | namespace DynamicData.Tests.List;
9 |
10 | public class CastFixture : IDisposable
11 | {
12 | private readonly ChangeSetAggregator _results;
13 |
14 | private readonly ISourceList _source;
15 |
16 | public CastFixture()
17 | {
18 | _source = new SourceList();
19 | _results = _source.Cast(i => (decimal)i).AsAggregator();
20 | }
21 |
22 | [Fact]
23 | public void CanCast()
24 | {
25 | _source.AddRange(Enumerable.Range(1, 10));
26 | _results.Data.Count.Should().Be(10);
27 |
28 | _source.Clear();
29 | _results.Data.Count.Should().Be(0);
30 | }
31 |
32 | public void Dispose()
33 | {
34 | _source.Dispose();
35 | _results.Dispose();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/CreationFixtures.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Linq;
3 | using System.Threading.Tasks;
4 |
5 | using FluentAssertions;
6 |
7 | using Xunit;
8 |
9 | namespace DynamicData.Tests.List;
10 |
11 | public class ListCreationFixtures
12 | {
13 | [Fact]
14 | public void Create()
15 | {
16 | static Task CreateTask(T value) => Task.FromResult(value);
17 |
18 | SubscribeAndAssert(
19 | ObservableChangeSet.Create(
20 | async list =>
21 | {
22 | var value = await CreateTask(10);
23 | list.Add(value);
24 | return () => { };
25 | }));
26 | }
27 |
28 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Accetable for test.")]
29 | private void SubscribeAndAssert(IObservable> observableChangeset, bool expectsError = false)
30 | where T : notnull
31 | {
32 | Exception? error = null;
33 | var complete = false;
34 | IChangeSet? changes = null;
35 |
36 | using (var myList = observableChangeset.Finally(() => complete = true).AsObservableList())
37 | using (myList.Connect().Subscribe(result => changes = result, ex => error = ex))
38 | {
39 | if (!expectsError)
40 | {
41 | error.Should().BeNull();
42 | }
43 | else
44 | {
45 | error.Should().NotBeNull();
46 | }
47 | }
48 |
49 | complete.Should().BeTrue();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/MergeManyChangeSetsFixture.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using FluentAssertions;
3 |
4 | using Xunit;
5 |
6 | namespace DynamicData.Tests.List;
7 |
8 | public class MergeManyChangeSetsFixture
9 | {
10 | [Fact]
11 | public void MergeManyShouldWork()
12 | {
13 | var a = new SourceList();
14 | var b = new SourceList();
15 | var c = new SourceList();
16 |
17 | var parent = new SourceList>();
18 | parent.Add(a);
19 | parent.Add(b);
20 | parent.Add(c);
21 |
22 | var d = parent.Connect().MergeMany(e => e.Connect().RemoveIndex()).AsObservableList();
23 |
24 | d.Count.Should().Be(0);
25 |
26 | a.Add(1);
27 |
28 | d.Count.Should().Be(1);
29 | a.Add(2);
30 | d.Count.Should().Be(2);
31 |
32 | b.Add(3);
33 | d.Count.Should().Be(3);
34 | b.Add(5);
35 | d.Count.Should().Be(4);
36 | new[] { 1, 2, 3, 5 }.Should().BeEquivalentTo(d.Items);
37 |
38 | b.Clear();
39 |
40 | // Fails below
41 | d.Count.Should().Be(2);
42 | new[] { 1, 2 }.Should().BeEquivalentTo(d.Items);
43 |
44 | a.ReplaceAt(0,100);
45 | new[] { 2, 100 }.Should().BeEquivalentTo(d.Items);
46 |
47 |
48 | var f = new SourceList();
49 | f.AddRange(Enumerable.Range(10,5));
50 | parent.ReplaceAt(2,f);
51 |
52 |
53 | new[] { 2, 100, 10,11,12,13,14 }.Should().BeEquivalentTo(d.Items);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/OnItemFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using DynamicData.Tests.Domain;
4 |
5 | using Xunit;
6 |
7 | namespace DynamicData.Tests.List;
8 |
9 | public class OnItemFixture
10 | {
11 | [Fact]
12 | public void OnItemAddCalled()
13 | {
14 | var called = false;
15 | var source = new SourceList();
16 |
17 | source.Connect().OnItemAdded(_ => called = true).Subscribe();
18 |
19 | var person = new Person("A", 1);
20 |
21 | source.Add(person);
22 | Assert.True(called);
23 | }
24 |
25 | [Fact]
26 | public void OnItemRefreshedCalled()
27 | {
28 | var called = false;
29 | var source = new SourceList();
30 |
31 | var person = new Person("A", 1);
32 | source.Add(person);
33 |
34 | source.Connect().AutoRefresh(x=>x.Age).OnItemRefreshed(_ => called = true).Subscribe();
35 |
36 | person.Age += 1;
37 |
38 | Assert.True(called);
39 | }
40 |
41 | [Fact]
42 | public void OnItemRemovedCalled()
43 | {
44 | var called = false;
45 | var source = new SourceList();
46 |
47 | source.Connect().OnItemRemoved(_ => called = true).Subscribe();
48 |
49 | var person = new Person("A", 1);
50 | source.Add(person);
51 | source.Remove(person);
52 | Assert.True(called);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/RemoveManyFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using FluentAssertions;
6 |
7 | using Xunit;
8 |
9 | namespace DynamicData.Tests.List;
10 |
11 | public class RemoveManyFixture
12 | {
13 | private readonly List _list;
14 |
15 | public RemoveManyFixture() => _list = new List();
16 |
17 | [Fact]
18 | public void DoesNotRemoveDuplicates()
19 | {
20 | _list.AddRange(new[] { 1, 1, 1, 5, 6, 7 });
21 | _list.RemoveMany(new[] { 1, 1, 7 });
22 | _list.Should().BeEquivalentTo(new[] { 1, 5, 6 });
23 | }
24 |
25 | [Fact]
26 | public void RemoveLargeBatch()
27 | {
28 | var toAdd = Enumerable.Range(1, 10000).ToArray();
29 | _list.AddRange(toAdd);
30 |
31 | var toRemove = _list.Take(_list.Count / 2).OrderBy(x => Guid.NewGuid()).ToArray();
32 | _list.RemoveMany(toRemove);
33 | _list.Should().BeEquivalentTo(toAdd.Except(toRemove));
34 | }
35 |
36 | [Fact]
37 | public void RemoveManyWillRemoveARange()
38 | {
39 | _list.AddRange(Enumerable.Range(1, 10));
40 | _list.RemoveMany(Enumerable.Range(2, 8));
41 | _list.Should().BeEquivalentTo(new[] { 1, 10 });
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/SortPrimitiveFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using DynamicData.Binding;
6 |
7 | using FluentAssertions;
8 |
9 | using Xunit;
10 |
11 | namespace DynamicData.Tests.List;
12 |
13 | public class SortPrimitiveFixture : IDisposable
14 | {
15 | private readonly IComparer _comparer = SortExpressionComparer.Ascending(i => i);
16 |
17 | private readonly ChangeSetAggregator _results;
18 |
19 | private readonly ISourceList _source;
20 |
21 | public SortPrimitiveFixture()
22 | {
23 | _source = new SourceList();
24 | _results = _source.Connect().Sort(_comparer).AsAggregator();
25 | }
26 |
27 | public void Dispose()
28 | {
29 | _results.Dispose();
30 | _source.Dispose();
31 | }
32 |
33 | [Fact]
34 | public void RemoveRandomSorts()
35 | {
36 | //seems an odd test but believe me it catches exceptions when sorting on primitives
37 | var items = Enumerable.Range(1, 100).OrderBy(_ => Guid.NewGuid()).ToArray();
38 | _source.AddRange(items);
39 |
40 | _results.Data.Count.Should().Be(100);
41 |
42 | var expectedResult = items.OrderBy(p => p, _comparer);
43 | var actualResult = _results.Data.Items;
44 |
45 | actualResult.Should().BeEquivalentTo(expectedResult);
46 |
47 | for (var i = 0; i < 50; i++)
48 | {
49 | _source.Remove(i);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/SourceListFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using FluentAssertions;
5 | using Xunit;
6 |
7 | namespace DynamicData.Tests.List;
8 |
9 | public class SourceListFixture
10 | {
11 | [Fact]
12 | public void InitialChangeIsRange()
13 | {
14 | var source = new SourceList();
15 | source.Add("A");
16 | var changeSets = new List>();
17 |
18 | source.Connect().Subscribe(changeSets.Add).Dispose();
19 |
20 |
21 | changeSets[0].First().Type.Should().Be(ChangeType.Range);
22 | changeSets[0].First().Range.Index.Should().Be(0);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/List/ToCollectionFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reactive.Linq;
4 | using FluentAssertions;
5 | using Xunit;
6 |
7 | namespace DynamicData.Tests.List;
8 |
9 | public class ToCollectionFixture
10 | {
11 | [Fact]
12 | public void ToCollectionTest()
13 | {
14 | var list = new SourceList();
15 | // var collection = Observable.Defer(() => list.Connect().ToCollection());
16 | var collection = list.Connect().ToCollection();
17 | IReadOnlyCollection? res1 = null;
18 | IReadOnlyCollection? res2 = null;
19 | collection.Subscribe(x => res1 = x);
20 | collection.Subscribe(x => res2 = x);
21 | list.Add("1");
22 | list.Add("2");
23 | res1?.Count.Should().Be(2);
24 | res2?.Count.Should().Be(2);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/ComparerExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | namespace DynamicData.Tests.Utilities;
6 |
7 | internal sealed class NoOpComparer : IComparer
8 | {
9 | public int Compare(T x, T y) => throw new NotImplementedException();
10 | }
11 |
12 | internal sealed class NoOpEqualityComparer : IEqualityComparer
13 | {
14 | [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "Suppressed for Net 9.0")]
15 | public bool Equals(T x, T y) => throw new NotImplementedException();
16 | [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "Suppressed for Net 9.0")]
17 | public int GetHashCode([DisallowNull] T obj) => throw new NotImplementedException();
18 | }
19 |
20 |
21 | internal sealed class InvertedComparer(IComparer original) : IComparer
22 | {
23 | private readonly IComparer _original = original;
24 |
25 | public int Compare(T x, T y) => _original.Compare(x, y) * -1;
26 | }
27 |
28 |
29 | internal static class ComparerExtensions
30 | {
31 | public static IComparer Invert(this IComparer comparer) => new InvertedComparer(comparer);
32 | }
33 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/FakerExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reactive.Linq;
4 | using System.Reactive.Concurrency;
5 | using Bogus;
6 |
7 | namespace DynamicData.Tests.Utilities;
8 |
9 | internal static class FakerExtensions
10 | {
11 | public static IObservable IntervalGenerate(this Faker faker, Randomizer randomizer, TimeSpan maxTime, IScheduler? scheduler = null)
12 | where T : class =>
13 | randomizer.Interval(maxTime, scheduler).Select(_ => faker.Generate());
14 |
15 | public static IObservable IntervalGenerate(this Faker faker, TimeSpan period, IScheduler? scheduler = null)
16 | where T : class =>
17 | Observable.Interval(period, scheduler ?? DefaultScheduler.Instance).Select(_ => faker.Generate());
18 | }
19 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/FunctionalExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DynamicData.Tests.Utilities;
4 |
5 | internal static class FunctionalExtensions
6 | {
7 | public static T With(this T item, Action action)
8 | {
9 | action(item);
10 | return item;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/ObservableEx.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Concurrency;
3 | using System.Reactive.Linq;
4 |
5 | namespace DynamicData.Tests.Utilities;
6 |
7 | ///
8 | /// Extra Observable Tools for Testing
9 | ///
10 | internal static class ObservableEx
11 | {
12 | ///
13 | /// Like except the interval time is recomputed each time.
14 | ///
15 | /// Function to get the next interval.
16 | /// Scheduler to use for firing events
17 | /// IObservable{long} instance.
18 | public static IObservable Interval(Func nextInterval, IScheduler? scheduler = null) =>
19 | Observable.Create(observer =>
20 | {
21 | _ = nextInterval ?? throw new ArgumentNullException(nameof(nextInterval));
22 |
23 | IDisposable ScheduleFirst(IScheduler sch)
24 | {
25 | IDisposable HandleNext(IScheduler _, long counter)
26 | {
27 | observer.OnNext(counter);
28 | return ScheduleNext(sch, counter + 1);
29 | }
30 |
31 | IDisposable ScheduleNext(IScheduler _, long counter) => sch.Schedule(counter, nextInterval(), HandleNext);
32 |
33 | return sch.Schedule(0, nextInterval(), HandleNext);
34 | }
35 |
36 | return ScheduleFirst(scheduler ?? DefaultScheduler.Instance);
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/RandomizerExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Reactive.Concurrency;
4 | using Bogus;
5 |
6 | namespace DynamicData.Tests.Utilities;
7 |
8 | internal static class RandomizerExtensions
9 | {
10 | public static TimeSpan TimeSpan(this Randomizer randomizer, TimeSpan minTime, TimeSpan maxTime) => System.TimeSpan.FromTicks(randomizer.Long(minTime.Ticks, maxTime.Ticks));
11 |
12 | public static TimeSpan TimeSpan(this Randomizer randomizer, TimeSpan maxTime) => TimeSpan(randomizer, System.TimeSpan.Zero, maxTime);
13 |
14 | public static bool CoinFlip(this Randomizer randomizer, Action action)
15 | {
16 | if (randomizer.Bool())
17 | {
18 | action();
19 | return true;
20 | }
21 |
22 | return false;
23 | }
24 |
25 | public static bool Chance(this Randomizer randomizer, double chancePercent, Action action)
26 | {
27 | Debug.Assert(chancePercent >= 0.0 && chancePercent <= 1.0);
28 | if (randomizer.Double() <= chancePercent)
29 | {
30 | action();
31 | return true;
32 | }
33 |
34 | return false;
35 | }
36 |
37 | public static IObservable Interval(this Randomizer randomizer, TimeSpan minTime, TimeSpan maxTime, IScheduler? scheduler = null) =>
38 | ObservableEx.Interval(() => randomizer.TimeSpan(minTime, maxTime), scheduler);
39 |
40 | public static IObservable Interval(this Randomizer randomizer, TimeSpan maxTime, IScheduler? scheduler = null) =>
41 | Interval(randomizer, System.TimeSpan.Zero, maxTime, scheduler);
42 | }
43 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/RawAnonymousObservable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DynamicData.Tests.Utilities;
4 |
5 | internal static class RawAnonymousObservable
6 | {
7 | public static RawAnonymousObservable Create(Func, IDisposable> onSubscribe)
8 | => new(onSubscribe);
9 | }
10 |
11 | // Allows bypassing of safeguards implemented within Observable.Create(), for testing.
12 | internal class RawAnonymousObservable
13 | : IObservable
14 | {
15 | private readonly Func, IDisposable> _onSubscribe;
16 |
17 | public RawAnonymousObservable(Func, IDisposable> onSubscribe)
18 | => _onSubscribe = onSubscribe;
19 |
20 | public IDisposable Subscribe(IObserver observer)
21 | => _onSubscribe.Invoke(observer);
22 | }
23 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/RawAnonymousObserver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DynamicData.Tests.Utilities;
4 |
5 | internal static class RawAnonymousObserver
6 | {
7 | public static RawAnonymousObserver Create(
8 | Action onNext,
9 | Action onError,
10 | Action onCompleted)
11 | => new(
12 | onNext: onNext,
13 | onError: onError,
14 | onCompleted: onCompleted);
15 | }
16 |
17 | // Allows bypassing of safeguards implemented within Observer.Create(), for testing.
18 | internal class RawAnonymousObserver
19 | : IObserver
20 | {
21 | private readonly Action _onCompleted;
22 | private readonly Action _onError;
23 | private readonly Action _onNext;
24 |
25 | public RawAnonymousObserver(
26 | Action onNext,
27 | Action onError,
28 | Action onCompleted)
29 | {
30 | _onNext = onNext;
31 | _onError = onError;
32 | _onCompleted = onCompleted;
33 | }
34 |
35 | public void OnCompleted()
36 | => _onCompleted.Invoke();
37 |
38 | public void OnError(Exception error)
39 | => _onError.Invoke(error);
40 |
41 | public void OnNext(T value)
42 | => _onNext.Invoke(value);
43 | }
44 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/UnsynchronizedNotificationException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive;
3 |
4 | namespace DynamicData.Tests.Utilities;
5 |
6 | public class UnsynchronizedNotificationException
7 | : Exception
8 | {
9 | public UnsynchronizedNotificationException()
10 | : base("Unsynchronized notification received: Another notification is already being processed")
11 | { }
12 |
13 | public required Notification IncomingNotification { get; init; }
14 |
15 | public required Notification PriorNotification { get; init; }
16 | }
17 |
--------------------------------------------------------------------------------
/src/DynamicData.Tests/Utilities/ValueRecordingObserver.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reactive.Concurrency;
3 |
4 | namespace DynamicData.Tests.Utilities;
5 |
6 | public sealed class ValueRecordingObserver
7 | : RecordingObserverBase
8 | {
9 | private readonly List _recordedValues;
10 |
11 | public ValueRecordingObserver(IScheduler scheduler)
12 | : base(scheduler)
13 | => _recordedValues = new();
14 |
15 | public IReadOnlyList RecordedValues
16 | => _recordedValues;
17 |
18 | protected override void OnNext(T value)
19 | {
20 | if (!HasFinalized)
21 | _recordedValues.Add(value);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/DynamicData/Aggregation/AggregateType.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Aggregation;
6 |
7 | ///
8 | /// The type of aggregation.
9 | ///
10 | public enum AggregateType
11 | {
12 | ///
13 | /// The add.
14 | ///
15 | Add,
16 |
17 | ///
18 | /// The remove.
19 | ///
20 | Remove
21 | }
22 |
--------------------------------------------------------------------------------
/src/DynamicData/Aggregation/Avg.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Aggregation;
6 |
7 | internal readonly struct Avg(int count, TValue sum)
8 | {
9 | public int Count { get; } = count;
10 |
11 | public TValue Sum { get; } = sum;
12 | }
13 |
--------------------------------------------------------------------------------
/src/DynamicData/Aggregation/IAggregateChangeSet.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Aggregation;
6 |
7 | ///
8 | /// A change set which has been shaped for rapid online aggregations.
9 | ///
10 | /// The type of the item.
11 | public interface IAggregateChangeSet : IEnumerable>
12 | {
13 | }
14 |
--------------------------------------------------------------------------------
/src/DynamicData/Aggregation/StdDev.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Aggregation;
6 |
7 | internal readonly struct StdDev(int count, TValue sumOfItems, TValue sumOfSquares)
8 | {
9 | public int Count { get; } = count;
10 |
11 | public TValue SumOfItems { get; } = sumOfItems;
12 |
13 | public TValue SumOfSquares { get; } = sumOfSquares;
14 | }
15 |
--------------------------------------------------------------------------------
/src/DynamicData/Attributes.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.Runtime.CompilerServices;
6 |
7 | [assembly: InternalsVisibleTo("DynamicData.Tests")]
8 | [assembly: InternalsVisibleTo("DynamicData.ReactiveUI")]
9 | [assembly: InternalsVisibleTo("DynamicData.Profile")]
--------------------------------------------------------------------------------
/src/DynamicData/Binding/BindingListEventsSuspender.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.ComponentModel;
6 | using System.Diagnostics.CodeAnalysis;
7 | using System.Reactive.Disposables;
8 |
9 | namespace DynamicData.Binding;
10 |
11 | internal sealed class BindingListEventsSuspender<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : IDisposable
12 | {
13 | private readonly IDisposable _cleanUp;
14 |
15 | public BindingListEventsSuspender(BindingList list)
16 | {
17 | list.RaiseListChangedEvents = false;
18 |
19 | _cleanUp = Disposable.Create(
20 | () =>
21 | {
22 | list.RaiseListChangedEvents = true;
23 | list.ResetBindings();
24 | });
25 | }
26 |
27 | public void Dispose() => _cleanUp.Dispose();
28 | }
29 |
--------------------------------------------------------------------------------
/src/DynamicData/Binding/IEvaluateAware.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Binding;
6 |
7 | ///
8 | /// Implement on an object and use in conjunction with InvokeEvaluate operator
9 | /// to make an object aware of any evaluates.
10 | ///
11 | public interface IEvaluateAware
12 | {
13 | ///
14 | /// Refresh method.
15 | ///
16 | void Evaluate();
17 | }
18 |
--------------------------------------------------------------------------------
/src/DynamicData/Binding/IIndexAware.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Binding;
6 |
7 | ///
8 | /// Implement on an object and use in conjunction with UpdateIndex operator
9 | /// to make an object aware of it's sorted index.
10 | ///
11 | public interface IIndexAware
12 | {
13 | ///
14 | /// Gets or sets the index.
15 | ///
16 | ///
17 | /// The index.
18 | ///
19 | int Index { get; set; }
20 | }
21 |
--------------------------------------------------------------------------------
/src/DynamicData/Binding/INotifyCollectionChangedSuspender.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Binding;
6 |
7 | ///
8 | /// Represents an observable collection where collection changed and count notifications can be suspended.
9 | ///
10 | public interface INotifyCollectionChangedSuspender
11 | {
12 | ///
13 | /// Suspends count notifications.
14 | ///
15 | /// A disposable which when disposed re-activates count notifications.
16 | IDisposable SuspendCount();
17 |
18 | ///
19 | /// Suspends notifications. When disposed, a reset notification is fired.
20 | ///
21 | /// A disposable which when disposed re-activates notifications.
22 | IDisposable SuspendNotifications();
23 | }
24 |
--------------------------------------------------------------------------------
/src/DynamicData/Binding/IObservableCollection.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.Collections.Specialized;
6 | using System.ComponentModel;
7 |
8 | namespace DynamicData.Binding;
9 |
10 | ///
11 | /// An override of observable collection which allows the suspension of notifications.
12 | ///
13 | /// The type of the item.
14 | public interface IObservableCollection : INotifyCollectionChanged, INotifyPropertyChanged, IList, INotifyCollectionChangedSuspender
15 | {
16 | ///
17 | /// Clears the list and Loads the specified items.
18 | ///
19 | /// The items.
20 | void Load(IEnumerable items);
21 |
22 | ///
23 | /// Moves the item at the specified index to a new location in the collection.
24 | ///
25 | /// The zero-based index specifying the location of the item to be moved.
26 | /// The zero-based index specifying the new location of the item.
27 | void Move(int oldIndex, int newIndex);
28 | }
29 |
--------------------------------------------------------------------------------
/src/DynamicData/Binding/IObservableCollectionAdaptor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Binding;
6 |
7 | ///
8 | /// Represents an adaptor which is used to update observable collection from
9 | /// a change set stream.
10 | ///
11 | /// The type of the object.
12 | /// The type of the key.
13 | public interface IObservableCollectionAdaptor
14 | where TObject : notnull
15 | where TKey : notnull
16 | {
17 | ///
18 | /// Maintains the specified collection from the changes.
19 | ///
20 | /// The changes.
21 | /// The collection.
22 | void Adapt(IChangeSet changes, IObservableCollection collection);
23 | }
24 |
--------------------------------------------------------------------------------
/src/DynamicData/Binding/ISortedObservableCollectionAdaptor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | namespace DynamicData.Binding;
6 |
7 | ///
8 | /// Represents an adaptor which is used to update observable collection from
9 | /// a sorted change set stream.
10 | ///
11 | /// The type of the object.
12 | /// The type of the key.
13 | public interface ISortedObservableCollectionAdaptor
14 | where TObject : notnull
15 | where TKey : notnull
16 | {
17 | ///
18 | /// Maintains the specified collection from the changes.
19 | ///
20 | /// The changes.
21 | /// The collection.
22 | void Adapt(ISortedChangeSet changes, IObservableCollection collection);
23 | }
24 |
--------------------------------------------------------------------------------
/src/DynamicData/Binding/Observable.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.Reactive.Linq;
6 |
7 | namespace DynamicData.Binding;
8 |
9 | internal static class Observable
10 | {
11 | public static IObservable Default { get; } = Observable.Return(default);
12 |
13 | public static IObservable Empty { get; } = Observable.Empty();
14 |
15 | public static IObservable Never { get; } = Observable.Never();
16 | }
17 |
--------------------------------------------------------------------------------
/src/DynamicData/Binding/ObservablePropertyPart.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2011-2023 Roland Pheasant. All rights reserved.
2 | // Roland Pheasant licenses this file to you under the MIT license.
3 | // See the LICENSE file in the project root for full license information.
4 |
5 | using System.Diagnostics;
6 | using System.Linq.Expressions;
7 | using System.Reactive;
8 |
9 | namespace DynamicData.Binding;
10 |
11 | [DebuggerDisplay("ObservablePropertyPart<{" + nameof(expression) + "}>")]
12 | internal sealed class ObservablePropertyPart(MemberExpression expression)
13 | {
14 | public Func