├── .gitignore ├── .gitmodules ├── .vsts.yaml ├── Directory.build.props ├── ExRam.ReactiveCollections.Tests ├── CollectionChangedNotificationTest.Current_is_set.verified.txt ├── CollectionChangedNotificationTest.cs ├── DictionaryReactiveCollectionSourceTest.Add.verified.txt ├── DictionaryReactiveCollectionSourceTest.AddRange1.verified.txt ├── DictionaryReactiveCollectionSourceTest.AddRange2.verified.txt ├── DictionaryReactiveCollectionSourceTest.Add_null.verified.txt ├── DictionaryReactiveCollectionSourceTest.Clear.verified.txt ├── DictionaryReactiveCollectionSourceTest.Contains.verified.txt ├── DictionaryReactiveCollectionSourceTest.Count_reflects_actual_count.verified.txt ├── DictionaryReactiveCollectionSourceTest.First_notification_is_reset.verified.txt ├── DictionaryReactiveCollectionSourceTest.Item.verified.txt ├── DictionaryReactiveCollectionSourceTest.Remove.verified.txt ├── DictionaryReactiveCollectionSourceTest.RemoveRange.verified.txt ├── DictionaryReactiveCollectionSourceTest.SetItem.verified.txt ├── DictionaryReactiveCollectionSourceTest.SetItems.verified.txt ├── DictionaryReactiveCollectionSourceTest.TryGetValue.verified.txt ├── DictionaryReactiveCollectionSourceTest.cs ├── ExRam.ReactiveCollections.Tests.csproj ├── ListReactiveCollectionSourceTest.Add.verified.txt ├── ListReactiveCollectionSourceTest.AddRange.verified.txt ├── ListReactiveCollectionSourceTest.Clear.verified.txt ├── ListReactiveCollectionSourceTest.Contains.verified.txt ├── ListReactiveCollectionSourceTest.CopyTo.verified.txt ├── ListReactiveCollectionSourceTest.First_notification_is_reset.verified.txt ├── ListReactiveCollectionSourceTest.IndexOf.verified.txt ├── ListReactiveCollectionSourceTest.Insert.verified.txt ├── ListReactiveCollectionSourceTest.InsertRange.verified.txt ├── ListReactiveCollectionSourceTest.IsReadOnly.verified.txt ├── ListReactiveCollectionSourceTest.Remove.verified.txt ├── ListReactiveCollectionSourceTest.RemoveAll.verified.txt ├── ListReactiveCollectionSourceTest.RemoveAt.verified.txt ├── ListReactiveCollectionSourceTest.RemoveRange.verified.txt ├── ListReactiveCollectionSourceTest.RemoveRange2.verified.txt ├── ListReactiveCollectionSourceTest.Replace.verified.txt ├── ListReactiveCollectionSourceTest.Reverse.verified.txt ├── ListReactiveCollectionSourceTest.SetItem.verified.txt ├── ListReactiveCollectionSourceTest.Sort.verified.txt ├── ListReactiveCollectionSourceTest.Sort_with_comparison.verified.txt ├── ListReactiveCollectionSourceTest.cs ├── ObservableExtensionsTest.cs ├── PerformanceTest.cs ├── ReactiveCollectionExtensionsTest.Add_to_filtered_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_filtered_list.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_filtered_set.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_projected_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_projected_list.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_projected_set.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_setSorted_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_setSorted_dictionary_with_duplicate_value.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_sorted_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_sorted_list.verified.txt ├── ReactiveCollectionExtensionsTest.Add_to_sorted_list_with_Changes.verified.txt ├── ReactiveCollectionExtensionsTest.Concat_Add.verified.txt ├── ReactiveCollectionExtensionsTest.Concat_Add_Delayed.verified.txt ├── ReactiveCollectionExtensionsTest.Concat_Add_Delayed2.verified.txt ├── ReactiveCollectionExtensionsTest.Concat_Clear.verified.txt ├── ReactiveCollectionExtensionsTest.Concat_Remove.verified.txt ├── ReactiveCollectionExtensionsTest.Concat_Replace_multiple.verified.txt ├── ReactiveCollectionExtensionsTest.Concat_Replace_single.verified.txt ├── ReactiveCollectionExtensionsTest.List_ToObservableCollection_Add.verified.txt ├── ReactiveCollectionExtensionsTest.List_ToObservableCollection_Clear.verified.txt ├── ReactiveCollectionExtensionsTest.List_ToObservableCollection_Remove.verified.txt ├── ReactiveCollectionExtensionsTest.List_ToObservableCollection_Replace.verified.txt ├── ReactiveCollectionExtensionsTest.List_ToObservableCollection_does_not_raise_events_on_event_subscription.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_from_filtered_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_from_filtered_list.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_from_filtered_set.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_from_projected_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_from_projected_list.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_from_projected_set.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_from_sorted_list.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_from_sorted_list_with_Changes.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_in_setSorted_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Remove_in_sorted_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_filtered_dictionary_addition.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_filtered_dictionary_removal.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_filtered_dictionary_replacement.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_filtered_from_ListChangedNotification_observable.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_filtered_list_addition.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_filtered_list_removal.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_filtered_list_replacement.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_filtered_list_replacement2.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_projected_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_projected_from_ListChangedNotification_observable.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_projected_from_SortedSetChangedNotification_observable.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_projected_list.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_setSorted_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_setSorted_projected_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_setSorted_projected_list.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_sorted_dictionary.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_sorted_from_ListChangedNotification_observable.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_sorted_list.verified.txt ├── ReactiveCollectionExtensionsTest.Replace_in_sorted_list_with_Changes.verified.txt ├── ReactiveCollectionExtensionsTest.Select_after_Select_on_dictionaries_behaves_correctly.verified.txt ├── ReactiveCollectionExtensionsTest.Select_after_SortSet_preserves_order.verified.txt ├── ReactiveCollectionExtensionsTest.Select_after_Where_behaves_correctly.verified.txt ├── ReactiveCollectionExtensionsTest.Select_after_Where_on_dictionaries_behaves_correctly.verified.txt ├── ReactiveCollectionExtensionsTest.SortedSet_ToObservableCollection_Add.verified.txt ├── ReactiveCollectionExtensionsTest.SortedSet_ToObservableCollection_Clear.verified.txt ├── ReactiveCollectionExtensionsTest.SortedSet_ToObservableCollection_Remove.verified.txt ├── ReactiveCollectionExtensionsTest.SortedSet_ToObservableCollection_does_not_raise_events_on_event_subscription.verified.txt ├── ReactiveCollectionExtensionsTest.Where_after_Where_behaves_correctly.verified.txt ├── ReactiveCollectionExtensionsTest.Where_after_Where_on_dictionaries_behaves_correctly.verified.txt ├── ReactiveCollectionExtensionsTest.cs ├── SortedListReactiveCollectionSourceTest.Add.verified.txt ├── SortedListReactiveCollectionSourceTest.AddRange.verified.txt ├── SortedListReactiveCollectionSourceTest.Clear.verified.txt ├── SortedListReactiveCollectionSourceTest.First_notification_is_reset.verified.txt ├── SortedListReactiveCollectionSourceTest.Remove.verified.txt ├── SortedListReactiveCollectionSourceTest.RemoveAll.verified.txt ├── SortedListReactiveCollectionSourceTest.RemoveAt.verified.txt ├── SortedListReactiveCollectionSourceTest.RemoveRange.verified.txt ├── SortedListReactiveCollectionSourceTest.RemoveRange2.verified.txt ├── SortedListReactiveCollectionSourceTest.Replace.verified.txt ├── SortedListReactiveCollectionSourceTest.cs ├── SortedSetReactiveCollectionSourceTest.Add.verified.txt ├── SortedSetReactiveCollectionSourceTest.Add_multiple.verified.txt ├── SortedSetReactiveCollectionSourceTest.Clear.verified.txt ├── SortedSetReactiveCollectionSourceTest.Clear_preserves_comparer.verified.txt ├── SortedSetReactiveCollectionSourceTest.First_notification_is_reset.verified.txt ├── SortedSetReactiveCollectionSourceTest.Remove.verified.txt └── SortedSetReactiveCollectionSourceTest.cs ├── ExRam.ReactiveCollections.sln ├── ExRam.ReactiveCollections ├── ExRam.ReactiveCollections.csproj ├── Extensions │ ├── ComparisonExtensions.cs │ ├── ObservableExtensions (Multicast).cs │ ├── ObservableExtensions (Normalize).cs │ ├── ObservableExtensions (ToConnectableReactiveCollection).cs │ ├── ObservableExtensions (ToNotifyCollectionChangedEventPattern).cs │ ├── ObservableExtensions (ToReactiveCollection).cs │ ├── ReactiveCollectionExtensions (AsReactiveCollection).cs │ ├── ReactiveCollectionExtensions (Concat).cs │ ├── ReactiveCollectionExtensions (GetValueObservable).cs │ ├── ReactiveCollectionExtensions (ObserveOn).cs │ ├── ReactiveCollectionExtensions (Select).cs │ ├── ReactiveCollectionExtensions (Sort).cs │ ├── ReactiveCollectionExtensions (SortSet).cs │ ├── ReactiveCollectionExtensions (SubscribeOn).cs │ ├── ReactiveCollectionExtensions (ToObservableCollection).cs │ ├── ReactiveCollectionExtensions (Tranform).cs │ ├── ReactiveCollectionExtensions (Where).cs │ └── ReactiveCollectionExtensions (WithChanges).cs ├── Interfaces │ ├── ICollectionChangedNotification.cs │ ├── IConnectableReactiveCollection.cs │ ├── IIndexedCollectionChangedNotification.cs │ ├── IReactiveCollection.cs │ └── IReactiveCollectionSource.cs ├── Notifications │ ├── CollectionChangedNotification.cs │ ├── DictionaryChangedNotification.cs │ ├── ListChangedNotification.cs │ ├── SortedListChangedNotification.cs │ └── SortedSetChangedNotification.cs ├── Properties │ └── AssemblyInfo.cs └── ReactiveCollectionSources │ ├── DictionaryReactiveCollectionSource.cs │ ├── ListReactiveCollectionSource.cs │ ├── ReactiveCollectionSource.cs │ ├── SortedListReactiveCollectionSource.cs │ └── SortedSetReactiveCollectionSource.cs ├── LICENSE.md ├── README.md └── version.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | *.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.pfx 193 | *.publishsettings 194 | node_modules/ 195 | orleans.codegen.cs 196 | 197 | # Since there are multiple workflows, uncomment next line to ignore bower_components 198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 199 | #bower_components/ 200 | 201 | # RIA/Silverlight projects 202 | Generated_Code/ 203 | 204 | # Backup & report files from converting an old project file 205 | # to a newer Visual Studio version. Backup files are not needed, 206 | # because we have git ;-) 207 | _UpgradeReport_Files/ 208 | Backup*/ 209 | UpgradeLog*.XML 210 | UpgradeLog*.htm 211 | 212 | # SQL Server files 213 | *.mdf 214 | *.ldf 215 | 216 | # Business Intelligence projects 217 | *.rdl.data 218 | *.bim.layout 219 | *.bim_*.settings 220 | 221 | # Microsoft Fakes 222 | FakesAssemblies/ 223 | 224 | # GhostDoc plugin setting file 225 | *.GhostDoc.xml 226 | 227 | # Node.js Tools for Visual Studio 228 | .ntvs_analysis.dat 229 | 230 | # Visual Studio 6 build log 231 | *.plg 232 | 233 | # Visual Studio 6 workspace options file 234 | *.opt 235 | 236 | # Visual Studio LightSwitch build output 237 | **/*.HTMLClient/GeneratedArtifacts 238 | **/*.DesktopClient/GeneratedArtifacts 239 | **/*.DesktopClient/ModelManifest.xml 240 | **/*.Server/GeneratedArtifacts 241 | **/*.Server/ModelManifest.xml 242 | _Pvt_Extensions 243 | 244 | # Paket dependency manager 245 | .paket/paket.exe 246 | paket-files/ 247 | 248 | # FAKE - F# Make 249 | .fake/ 250 | 251 | # JetBrains Rider 252 | .idea/ 253 | *.sln.iml 254 | 255 | # CodeRush 256 | .cr/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExRam/ExRam.ReactiveCollections/da2d2a217c5344b69163d748f34d88ffabab5df1/.gitmodules -------------------------------------------------------------------------------- /.vsts.yaml: -------------------------------------------------------------------------------- 1 | pool: 2 | name: Hosted Windows 2019 with VS2019 3 | 4 | trigger: 5 | branches: 6 | include: 7 | - master 8 | paths: 9 | include: 10 | - '*' 11 | 12 | variables: 13 | BuildPlatform: 'any cpu' 14 | BuildConfiguration: 'release' 15 | 16 | steps: 17 | - task: NuGetToolInstaller@0 18 | displayName: Use NuGet 5.4.0 19 | inputs: 20 | versionSpec: 5.4.0 21 | 22 | - task: NuGetCommand@2 23 | displayName: NuGet restore 24 | inputs: 25 | restoreSolution: '**\*.sln' 26 | 27 | - task: VSBuild@1 28 | displayName: Build solution 29 | inputs: 30 | platform: '$(BuildPlatform)' 31 | configuration: '$(BuildConfiguration)' 32 | 33 | - task: VSTest@2 34 | displayName: Test 35 | inputs: 36 | testAssemblyVer2: '**\bin\$(BuildConfiguration)\**\*.Tests.dll' 37 | platform: '$(BuildPlatform)' 38 | configuration: '$(BuildConfiguration)' 39 | 40 | - task: CopyFiles@2 41 | displayName: Copy Files 42 | inputs: 43 | SourceFolder: '$(build.sourcesdirectory)' 44 | Contents: '**\bin\$(BuildConfiguration)\**\*.nupkg' 45 | TargetFolder: '$(build.artifactstagingdirectory)' 46 | 47 | - task: PublishBuildArtifacts@1 48 | displayName: 'Publish Artifact: drop' 49 | 50 | 51 | -------------------------------------------------------------------------------- /Directory.build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ExRam EDV GmbH & Co. KG 5 | ExRam EDV GmbH & Co. KG 6 | $(NoWarn);CS1998 7 | true 8 | 9.0 9 | enable 10 | $(DefineConstants);TRACE 11 | 12 | 13 | 14 | full 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/CollectionChangedNotificationTest.Current_is_set.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | 1, 3 | 2, 4 | 3 5 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/CollectionChangedNotificationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Collections.Specialized; 3 | using System.Threading.Tasks; 4 | using VerifyXunit; 5 | using Xunit; 6 | 7 | namespace ExRam.ReactiveCollections.Tests 8 | { 9 | public class CollectionChangedNotificationTest : VerifyBase 10 | { 11 | #region NotificationImpl 12 | private sealed class NotificationImpl : CollectionChangedNotification 13 | { 14 | // ReSharper disable once SuggestBaseTypeForParameter 15 | public NotificationImpl(ImmutableList current, NotifyCollectionChangedAction action, ImmutableList oldItems, ImmutableList newItems) : base(current, action, oldItems, newItems) 16 | { 17 | } 18 | 19 | public override ICollectionChangedNotification ToResetNotification() 20 | { 21 | return new NotificationImpl((ImmutableList)Current, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty); 22 | } 23 | } 24 | #endregion 25 | 26 | public CollectionChangedNotificationTest() : base() 27 | { 28 | 29 | } 30 | 31 | [Fact] 32 | public async Task Current_is_set() 33 | { 34 | var list = ImmutableList.Create(1, 2, 3); 35 | var notification = new NotificationImpl(list, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty); 36 | 37 | await Verify(notification.Current); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.Add.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: { 3 | Key: 1 4 | }, 5 | NewItems: [ 6 | { 7 | Key: Key, 8 | Value: 1 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.AddRange1.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: { 3 | Key3: 3, 4 | Key1: 1, 5 | Key2: 2 6 | }, 7 | NewItems: [ 8 | { 9 | Key: Key1, 10 | Value: 1 11 | }, 12 | { 13 | Key: Key2, 14 | Value: 2 15 | }, 16 | { 17 | Key: Key3, 18 | Value: 3 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.AddRange2.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: { 3 | 1: A, 4 | 2: BB, 5 | 3: CCC 6 | }, 7 | NewItems: [ 8 | { 9 | Key: 1, 10 | Value: A 11 | }, 12 | { 13 | Key: 2, 14 | Value: BB 15 | }, 16 | { 17 | Key: 3, 18 | Value: CCC 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.Add_null.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: { 3 | Key: null 4 | }, 5 | NewItems: [ 6 | { 7 | Key: Key, 8 | Value: null 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.Clear.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Action: Reset 3 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.Contains.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Item1: true 3 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.Count_reflects_actual_count.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Key3: 3, 3 | Key1: 1, 4 | Key2: 2 5 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.First_notification_is_reset.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Action: Reset 3 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.Item.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Item1: { 3 | And: { 4 | Subject: 1 5 | } 6 | }, 7 | Item2: { 8 | And: { 9 | Subject: 2 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.Remove.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | OldItems: [ 3 | { 4 | Key: Key1, 5 | Value: 1 6 | } 7 | ], 8 | Action: Remove 9 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.RemoveRange.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key3: 3, 8 | Key1: 1, 9 | Key2: 2 10 | }, 11 | NewItems: [ 12 | { 13 | Key: Key1, 14 | Value: 1 15 | }, 16 | { 17 | Key: Key2, 18 | Value: 2 19 | }, 20 | { 21 | Key: Key3, 22 | Value: 3 23 | } 24 | ] 25 | }, 26 | { 27 | Current: { 28 | Key3: 3 29 | }, 30 | OldItems: [ 31 | { 32 | Key: Key1, 33 | Value: 1 34 | }, 35 | { 36 | Key: Key2, 37 | Value: 2 38 | } 39 | ], 40 | Action: Remove 41 | } 42 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.SetItem.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: { 3 | Key3: 3, 4 | Key1: 1, 5 | Key2: 3 6 | }, 7 | OldItems: [ 8 | { 9 | Key: Key2, 10 | Value: 2 11 | } 12 | ], 13 | NewItems: [ 14 | { 15 | Key: Key2, 16 | Value: 3 17 | } 18 | ], 19 | Action: Replace 20 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.SetItems.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: { 3 | Key3: 6, 4 | Key1: 4, 5 | Key2: 5 6 | }, 7 | Action: Reset 8 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.TryGetValue.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Item1: true, 3 | Item2: 1 4 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/DictionaryReactiveCollectionSourceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reactive.Linq; 4 | using System.Reactive.Threading.Tasks; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using VerifyXunit; 8 | using Xunit; 9 | 10 | namespace ExRam.ReactiveCollections.Tests 11 | { 12 | internal sealed class DeterministicStringKeyComparer : IEqualityComparer 13 | { 14 | public static readonly DeterministicStringKeyComparer Instance = new(); 15 | 16 | private DeterministicStringKeyComparer() 17 | { 18 | 19 | } 20 | 21 | public bool Equals(string? x, string? y) 22 | { 23 | return StringComparer.Ordinal.Compare(x, y) == 0; 24 | } 25 | 26 | public int GetHashCode(string str) 27 | { 28 | unchecked 29 | { 30 | var hash1 = (5381 << 16) + 5381; 31 | var hash2 = hash1; 32 | 33 | for (var i = 0; i < str.Length; i += 2) 34 | { 35 | hash1 = ((hash1 << 5) + hash1) ^ str[i]; 36 | if (i == str.Length - 1) 37 | break; 38 | hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; 39 | } 40 | 41 | return hash1 + hash2 * 1566083941; 42 | } 43 | } 44 | } 45 | 46 | public class DictionaryReactiveCollectionSourceTest : VerifyBase 47 | { 48 | public DictionaryReactiveCollectionSourceTest() : base() 49 | { 50 | 51 | } 52 | 53 | [Fact] 54 | public async Task First_notification_is_reset() 55 | { 56 | var list = new DictionaryReactiveCollectionSource(); 57 | 58 | await Verify(await list.ReactiveCollection.Changes 59 | .FirstAsync() 60 | .ToTask()); 61 | } 62 | 63 | [Fact] 64 | public async Task Add() 65 | { 66 | var list = new DictionaryReactiveCollectionSource(); 67 | 68 | var notificationTask = list.ReactiveCollection.Changes 69 | .Skip(1) 70 | .FirstAsync() 71 | .ToTask(); 72 | 73 | list.Add("Key", 1); 74 | 75 | await Verify(notificationTask); 76 | } 77 | 78 | [Fact] 79 | public async Task Add_null() 80 | { 81 | var list = new DictionaryReactiveCollectionSource(); 82 | 83 | var notificationTask = list.ReactiveCollection.Changes 84 | .Skip(1) 85 | .FirstAsync() 86 | .ToTask(); 87 | 88 | list.Add("Key", null); 89 | 90 | await Verify(notificationTask); 91 | } 92 | 93 | [Fact] 94 | public void Adding_existing_key_throws() 95 | { 96 | Assert.Throws( 97 | () => 98 | { 99 | // ReSharper disable ObjectCreationAsStatement 100 | new DictionaryReactiveCollectionSource 101 | { 102 | { "Key", 1 }, 103 | { "Key", 2 } 104 | }; 105 | // ReSharper restore ObjectCreationAsStatement 106 | }); 107 | } 108 | 109 | [Fact] 110 | public async Task AddRange1() 111 | { 112 | var list = new DictionaryReactiveCollectionSource(DeterministicStringKeyComparer.Instance); 113 | 114 | var notificationTask = list.ReactiveCollection.Changes 115 | .Skip(1) 116 | .FirstAsync() 117 | .ToTask(); 118 | 119 | var range = new Dictionary 120 | { 121 | { "Key1", 1 }, 122 | { "Key2", 2 }, 123 | { "Key3", 3 } 124 | }; 125 | 126 | list.AddRange(range); 127 | 128 | await Verify(notificationTask); 129 | } 130 | 131 | [Fact] 132 | public async Task AddRange2() 133 | { 134 | var list = new DictionaryReactiveCollectionSource(); 135 | 136 | var notificationTask = list.ReactiveCollection.Changes 137 | .Skip(1) 138 | .FirstAsync() 139 | .ToTask(); 140 | 141 | var range = new[] 142 | { 143 | "A", 144 | "BB", 145 | "CCC" 146 | }; 147 | 148 | list.AddRange(range, x => x.Length); 149 | 150 | await Verify(notificationTask); 151 | } 152 | 153 | [Fact] 154 | public async Task Count_reflects_actual_count() 155 | { 156 | var list = new DictionaryReactiveCollectionSource(DeterministicStringKeyComparer.Instance) 157 | { 158 | { "Key1", 1 }, 159 | { "Key2", 2 }, 160 | { "Key3", 3 } 161 | }; 162 | 163 | await Verify(list); 164 | } 165 | 166 | [Fact] 167 | public async Task Clear() 168 | { 169 | var list = new DictionaryReactiveCollectionSource(); 170 | 171 | var notificationTask = list.ReactiveCollection.Changes 172 | .Skip(2) 173 | .FirstAsync() 174 | .ToTask(); 175 | 176 | list.Add("Key", 1); 177 | list.Clear(); 178 | 179 | await Verify(notificationTask); 180 | } 181 | 182 | [Fact] 183 | public async Task Contains() 184 | { 185 | var dict = new DictionaryReactiveCollectionSource 186 | { 187 | { "Key", 1 } 188 | }; 189 | 190 | await Verify(( 191 | dict.Contains(new KeyValuePair("Key", 1)), 192 | dict.Contains(new KeyValuePair("Key1", 2)))); 193 | } 194 | 195 | [Fact] 196 | public async Task Remove() 197 | { 198 | var list = new DictionaryReactiveCollectionSource(); 199 | 200 | var notificationTask = list.ReactiveCollection.Changes 201 | .Skip(2) 202 | .FirstAsync() 203 | .ToTask(); 204 | 205 | list.Add("Key1", 1); 206 | list.Remove("Key1"); 207 | 208 | await Verify(notificationTask); 209 | } 210 | 211 | [Fact] 212 | public async Task RemoveRange() 213 | { 214 | var list = new DictionaryReactiveCollectionSource(DeterministicStringKeyComparer.Instance); 215 | 216 | var notificationsTask = list.ReactiveCollection.Changes 217 | .Take(3) 218 | .ToArray() 219 | .ToTask(); 220 | 221 | list.AddRange(new[] 222 | { 223 | new KeyValuePair("Key1", 1), 224 | new KeyValuePair("Key2", 2), 225 | new KeyValuePair("Key3", 3) 226 | }); 227 | 228 | list.RemoveRange(new[] 229 | { 230 | "Key1", 231 | "Key2" 232 | }); 233 | 234 | await Verify(notificationsTask); 235 | } 236 | 237 | [Fact] 238 | public async Task SetItem() 239 | { 240 | var list = new DictionaryReactiveCollectionSource(DeterministicStringKeyComparer.Instance); 241 | 242 | var notificationTask = list.ReactiveCollection.Changes 243 | .Skip(2) 244 | .FirstAsync() 245 | .ToTask(); 246 | 247 | list.AddRange(new Dictionary 248 | { 249 | { "Key1", 1 }, 250 | { "Key2", 2 }, 251 | { "Key3", 3 } 252 | }); 253 | list.SetItem("Key2", 3); 254 | 255 | await Verify(notificationTask); 256 | } 257 | 258 | [Fact] 259 | public async Task SetItems() 260 | { 261 | var list = new DictionaryReactiveCollectionSource(DeterministicStringKeyComparer.Instance); 262 | 263 | var notificationTask = list.ReactiveCollection.Changes 264 | .Skip(2) 265 | .FirstAsync() 266 | .ToTask(); 267 | 268 | list.AddRange(new[] 269 | { 270 | new KeyValuePair("Key1", 1), 271 | new KeyValuePair("Key2", 2), 272 | new KeyValuePair("Key3", 3) 273 | }); 274 | 275 | list.SetItems(new[] 276 | { 277 | new KeyValuePair("Key1", 4), 278 | new KeyValuePair("Key2", 5), 279 | new KeyValuePair("Key3", 6) 280 | }); 281 | 282 | await Verify(notificationTask); 283 | } 284 | 285 | [Fact] 286 | public async Task TryGetValue() 287 | { 288 | var list = new DictionaryReactiveCollectionSource 289 | { 290 | { "Key1", 1 } 291 | }; 292 | 293 | await Verify(( 294 | list.TryGetValue("Key1", out var value), 295 | value, 296 | list.TryGetValue("Key2", out _))); 297 | } 298 | 299 | [Fact] 300 | public async Task Item() 301 | { 302 | var list = new DictionaryReactiveCollectionSource 303 | { 304 | { "Key1", 1 } 305 | }; 306 | 307 | var before = list["Key1"].Should().Be(1); 308 | list["Key1"] = 2; 309 | var after = list["Key1"].Should().Be(2); 310 | 311 | await Verify((before, after)); 312 | } 313 | 314 | [Fact] 315 | public void Item2() 316 | { 317 | var list = new DictionaryReactiveCollectionSource 318 | { 319 | { "Key1", 1 } 320 | }; 321 | 322 | Assert.Throws(() => 323 | { 324 | var v = list["Key"]; 325 | }); 326 | } 327 | 328 | [Fact] 329 | public void GetEnumerator() 330 | { 331 | var list = new DictionaryReactiveCollectionSource 332 | { 333 | { "Key1", 1 } 334 | }; 335 | 336 | using (var enumerator = list.GetEnumerator()) 337 | { 338 | enumerator.MoveNext().Should().BeTrue(); 339 | enumerator.Current.Should().Be(new KeyValuePair("Key1", 1)); 340 | enumerator.MoveNext().Should().BeFalse(); 341 | } 342 | } 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ExRam.ReactiveCollections.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ExRam.ReactiveCollections.Tests 5 | ExRam EDV GmbH & Co. KG 6 | net5.0 7 | $(NoWarn);CS1998 8 | ExRam.ReactiveCollections.Tests 9 | ExRam.ReactiveCollections.Tests 10 | true 11 | false 12 | false 13 | false 14 | false 15 | false 16 | false 17 | false 18 | false 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | all 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Add.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 0, 3 | Current: [ 4 | 1 5 | ], 6 | NewItems: [ 7 | 1 8 | ] 9 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.AddRange.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 0, 3 | Current: [ 4 | 1, 5 | 2, 6 | 3 7 | ], 8 | NewItems: [ 9 | 1, 10 | 2, 11 | 3 12 | ] 13 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Clear.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Action: Reset 3 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Contains.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Item1: true 3 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.CopyTo.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | 0, 3 | 0, 4 | 1, 5 | 2, 6 | 3 7 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.First_notification_is_reset.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Action: Reset 3 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.IndexOf.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Item1: 2, 3 | Item2: 1 4 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Insert.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 0, 3 | Current: [ 4 | 2 5 | ], 6 | NewItems: [ 7 | 2 8 | ] 9 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.InsertRange.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 1, 3 | Current: [ 4 | 1, 5 | 1, 6 | 2, 7 | 3 8 | ], 9 | NewItems: [ 10 | 1, 11 | 2, 12 | 3 13 | ] 14 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.IsReadOnly.verified.txt: -------------------------------------------------------------------------------- 1 | False -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Remove.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 0, 3 | OldItems: [ 4 | 1 5 | ], 6 | Action: Remove 7 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.RemoveAll.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: [ 3 | 1, 4 | 3 5 | ], 6 | Action: Reset 7 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.RemoveAt.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 1, 3 | Current: [ 4 | 1 5 | ], 6 | OldItems: [ 7 | 2 8 | ], 9 | Action: Remove 10 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.RemoveRange.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: [ 3 | 1, 4 | 3 5 | ], 6 | OldItems: [ 7 | 2, 8 | 4 9 | ], 10 | Action: Remove 11 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.RemoveRange2.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 2, 3 | Current: [ 4 | 1, 5 | 2 6 | ], 7 | OldItems: [ 8 | 3, 9 | 4 10 | ], 11 | Action: Remove 12 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Replace.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 0, 3 | Current: [ 4 | 5, 5 | 2, 6 | 3, 7 | 4, 8 | 1 9 | ], 10 | OldItems: [ 11 | 1 12 | ], 13 | NewItems: [ 14 | 5 15 | ], 16 | Action: Replace 17 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Reverse.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: [ 3 | 5, 4 | 4, 5 | 3, 6 | 2, 7 | 1 8 | ], 9 | Action: Reset 10 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.SetItem.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Index: 2, 3 | Current: [ 4 | 1, 5 | 2, 6 | 6, 7 | 4, 8 | 5 9 | ], 10 | OldItems: [ 11 | 3 12 | ], 13 | NewItems: [ 14 | 6 15 | ], 16 | Action: Replace 17 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Sort.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: [ 3 | 0, 4 | 1, 5 | 2, 6 | 3, 7 | 5 8 | ], 9 | Action: Reset 10 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.Sort_with_comparison.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Current: [ 3 | 5, 4 | 3, 5 | 2, 6 | 1, 7 | 0 8 | ], 9 | Action: Reset 10 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ListReactiveCollectionSourceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Reactive.Linq; 4 | using System.Reactive.Threading.Tasks; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using VerifyXunit; 8 | using Xunit; 9 | 10 | namespace ExRam.ReactiveCollections.Tests 11 | { 12 | public class ListReactiveCollectionSourceTest : VerifyBase 13 | { 14 | public ListReactiveCollectionSourceTest() : base() 15 | { 16 | 17 | } 18 | 19 | [Fact] 20 | public async Task First_notification_is_reset() 21 | { 22 | var list = new ListReactiveCollectionSource(); 23 | 24 | await Verify(list.ReactiveCollection.Changes.FirstAsync().ToTask()); 25 | } 26 | 27 | [Fact] 28 | public async Task Add() 29 | { 30 | var list = new ListReactiveCollectionSource(); 31 | 32 | var notificationTask = list.ReactiveCollection.Changes 33 | .Skip(1) 34 | .FirstAsync() 35 | .ToTask(); 36 | 37 | list.Add(1); 38 | 39 | await Verify(notificationTask); 40 | } 41 | 42 | [Fact] 43 | public async Task AddRange() 44 | { 45 | var list = new ListReactiveCollectionSource(); 46 | 47 | var notificationTask = list.ReactiveCollection.Changes 48 | .Skip(1) 49 | .FirstAsync() 50 | .ToTask(); 51 | 52 | var range = new[] { 1, 2, 3 }; 53 | list.AddRange(range); 54 | 55 | await Verify(notificationTask); 56 | } 57 | 58 | [Fact] 59 | public async Task Clear() 60 | { 61 | var list = new ListReactiveCollectionSource(); 62 | 63 | var notificationTask = list.ReactiveCollection.Changes 64 | .Skip(2) 65 | .FirstAsync() 66 | .ToTask(); 67 | 68 | list.Add(1); 69 | list.Clear(); 70 | 71 | await Verify(notificationTask); 72 | } 73 | 74 | [Fact] 75 | public async Task Contains() 76 | { 77 | var list = new ListReactiveCollectionSource 78 | { 79 | 1 80 | }; 81 | 82 | await Verify(( 83 | list.Contains(1), 84 | list.Contains(2))); 85 | } 86 | 87 | [Fact] 88 | public async Task CopyTo() 89 | { 90 | var list = new ListReactiveCollectionSource(); 91 | 92 | var range = new[] { 1, 2, 3 }; 93 | list.AddRange(range); 94 | 95 | var target = new int[5]; 96 | list.CopyTo(target, 2); 97 | 98 | await Verify(target); 99 | } 100 | 101 | [Fact] 102 | public void GetEnumerator() 103 | { 104 | var list = new ListReactiveCollectionSource 105 | { 106 | 1, 107 | 2, 108 | 3 109 | }; 110 | 111 | using (var enumerator = list.GetEnumerator()) 112 | { 113 | enumerator.MoveNext().Should().BeTrue(); 114 | enumerator.Current.Should().Be(1); 115 | 116 | enumerator.MoveNext().Should().BeTrue(); 117 | enumerator.Current.Should().Be(2); 118 | 119 | enumerator.MoveNext().Should().BeTrue(); 120 | enumerator.Current.Should().Be(3); 121 | 122 | enumerator.MoveNext().Should().BeFalse(); 123 | } 124 | } 125 | 126 | [Fact] 127 | public async Task IndexOf() 128 | { 129 | var list = new ListReactiveCollectionSource 130 | { 131 | 1, 2, 3 132 | }; 133 | 134 | await Verify(( 135 | list.IndexOf(3), 136 | list.IndexOf(2), 137 | list.IndexOf(1))); 138 | } 139 | 140 | [Fact] 141 | public async Task Insert() 142 | { 143 | var list = new ListReactiveCollectionSource(); 144 | 145 | var notificationTask = list.ReactiveCollection.Changes 146 | .Skip(1) 147 | .FirstAsync() 148 | .ToTask(); 149 | 150 | list.Insert(0, 2); 151 | 152 | await Verify(notificationTask); 153 | } 154 | 155 | [Fact] 156 | public async Task InsertRange() 157 | { 158 | var list = new ListReactiveCollectionSource(); 159 | 160 | var notificationTask = list.ReactiveCollection.Changes 161 | .Skip(2) 162 | .FirstAsync() 163 | .ToTask(); 164 | 165 | list.Add(1); 166 | 167 | var range = new[] { 1, 2, 3 }; 168 | list.InsertRange(1, range); 169 | 170 | await Verify(notificationTask); 171 | } 172 | 173 | [Fact] 174 | public async Task IsReadOnly() 175 | { 176 | var list = (IList)new ListReactiveCollectionSource(); 177 | 178 | await Verify(list.IsReadOnly); 179 | } 180 | 181 | [Fact] 182 | public void Item() 183 | { 184 | var list = (IList)new ListReactiveCollectionSource(); 185 | 186 | list 187 | .Invoking(_ => _.Add(1)) 188 | .Should() 189 | .Throw(); 190 | } 191 | 192 | [Fact] 193 | public async Task Remove() 194 | { 195 | var list = new ListReactiveCollectionSource(); 196 | 197 | var notificationTask = list.ReactiveCollection.Changes 198 | .Skip(2) 199 | .FirstAsync() 200 | .ToTask(); 201 | 202 | list.Add(1); 203 | list.Remove(1); 204 | 205 | await Verify(notificationTask); 206 | } 207 | 208 | [Fact] 209 | public async Task RemoveAll() 210 | { 211 | var list = new ListReactiveCollectionSource(); 212 | 213 | var notificationTask = list.ReactiveCollection.Changes 214 | .Skip(2) 215 | .FirstAsync() 216 | .ToTask(); 217 | 218 | list.AddRange(new[] { 1, 2, 3 }); 219 | list.RemoveAll(x => x % 2 == 0); 220 | 221 | await Verify(notificationTask); 222 | } 223 | 224 | [Fact] 225 | public async Task RemoveAt() 226 | { 227 | var list = new ListReactiveCollectionSource(); 228 | 229 | var notificationTask = list.ReactiveCollection.Changes 230 | .Skip(3) 231 | .FirstAsync() 232 | .ToTask(); 233 | 234 | list.Add(1); 235 | list.Add(2); 236 | list.RemoveAt(1); 237 | 238 | await Verify(notificationTask); 239 | } 240 | 241 | [Fact] 242 | public async Task RemoveRange() 243 | { 244 | var list = new ListReactiveCollectionSource(); 245 | 246 | var notificationTask = list.ReactiveCollection.Changes 247 | .Skip(2) 248 | .FirstAsync() 249 | .ToTask(); 250 | 251 | list.AddRange(new[] { 1, 2, 3, 4 }); 252 | list.RemoveRange(new[] { 2, 4 }); 253 | 254 | await Verify(notificationTask); 255 | } 256 | 257 | [Fact] 258 | public async Task RemoveRange2() 259 | { 260 | var list = new ListReactiveCollectionSource(); 261 | 262 | var notificationTask = list.ReactiveCollection.Changes 263 | .Skip(2) 264 | .FirstAsync() 265 | .ToTask(); 266 | 267 | list.AddRange(new[] { 1, 2, 3, 4 }); 268 | list.RemoveRange(2, 2); 269 | 270 | await Verify(notificationTask); 271 | } 272 | 273 | [Fact] 274 | public async Task Replace() 275 | { 276 | var list = new ListReactiveCollectionSource(); 277 | 278 | var notificationTask = list.ReactiveCollection.Changes 279 | .Skip(2) 280 | .FirstAsync() 281 | .ToTask(); 282 | 283 | list.AddRange(new[] { 1, 2, 3, 4, 1 }); 284 | list.Replace(1, 5); 285 | 286 | await Verify(notificationTask); 287 | } 288 | 289 | [Fact] 290 | public async Task Reverse() 291 | { 292 | var list = new ListReactiveCollectionSource(); 293 | 294 | var notificationTask = list.ReactiveCollection.Changes 295 | .Skip(2) 296 | .FirstAsync() 297 | .ToTask(); 298 | 299 | list.AddRange(new[] { 1, 2, 3, 4, 5 }); 300 | list.Reverse(); 301 | 302 | await Verify(notificationTask); 303 | } 304 | 305 | [Fact] 306 | public async Task SetItem() 307 | { 308 | var list = new ListReactiveCollectionSource(); 309 | 310 | var notificationTask = list.ReactiveCollection.Changes 311 | .Skip(2) 312 | .FirstAsync() 313 | .ToTask(); 314 | 315 | list.AddRange(new[] { 1, 2, 3, 4, 5 }); 316 | list.SetItem(2, 6); 317 | 318 | await Verify(notificationTask); 319 | } 320 | 321 | [Fact] 322 | public async Task Sort() 323 | { 324 | var list = new ListReactiveCollectionSource(); 325 | 326 | var notificationTask = list.ReactiveCollection.Changes 327 | .Skip(2) 328 | .FirstAsync() 329 | .ToTask(); 330 | 331 | list.AddRange(new[] { 2, 1, 3, 5, 0 }); 332 | list.Sort(); 333 | 334 | await Verify(notificationTask); 335 | } 336 | 337 | [Fact] 338 | public async Task Sort_with_comparison() 339 | { 340 | var list = new ListReactiveCollectionSource(); 341 | 342 | var notificationTask = list.ReactiveCollection.Changes 343 | .Skip(2) 344 | .FirstAsync() 345 | .ToTask(); 346 | 347 | list.AddRange(new[] { 2, 1, 3, 5, 0 }); 348 | list.Sort((x, y) => y.CompareTo(x)); 349 | 350 | await Verify(notificationTask); 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ObservableExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Subjects; 4 | using System.Reactive.Threading.Tasks; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using Xunit; 8 | 9 | namespace ExRam.ReactiveCollections.Tests 10 | { 11 | public class ObservableExtensionsTest 12 | { 13 | [Fact] 14 | public async Task ReplayFresh_provides_a_fresh_observable() 15 | { 16 | var sourceSubject = new Subject(); 17 | 18 | var replayed = sourceSubject 19 | .ReplayFresh(1) 20 | .RefCount(); 21 | 22 | using (replayed.Subscribe()) 23 | { 24 | var task1 = replayed 25 | .FirstAsync() 26 | .ToTask(); 27 | 28 | sourceSubject.OnNext(36); 29 | 30 | (await task1).Should().Be(36); 31 | (await replayed.FirstAsync().ToTask()).Should().Be(36); 32 | } 33 | 34 | var task2 = replayed 35 | .FirstAsync() 36 | .ToTask(); 37 | 38 | sourceSubject.OnNext(37); 39 | 40 | (await task2).Should().Be(37); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/PerformanceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Globalization; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Threading.Tasks; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace ExRam.ReactiveCollections.Tests 10 | { 11 | public class PerformanceTest 12 | { 13 | [Fact(Skip="")] 14 | public async Task DictionaryPerformanceTest() 15 | { 16 | const int count = 10000; 17 | var dict = new DictionaryReactiveCollectionSource(); 18 | 19 | var stopWatch = new Stopwatch(); 20 | var lastTask = dict.ReactiveCollection.Changes 21 | .Skip(count) 22 | .FirstAsync() 23 | .ToTask(); 24 | 25 | stopWatch.Start(); 26 | 27 | for (var i = 0; i < count; i++) 28 | { 29 | dict.Add(i.ToString(CultureInfo.InvariantCulture), i); 30 | } 31 | 32 | for (var i = count - 1; i >= 0; i--) 33 | { 34 | dict.Remove(i.ToString(CultureInfo.InvariantCulture)); 35 | } 36 | 37 | await lastTask; 38 | 39 | stopWatch.Stop(); 40 | Console.WriteLine(stopWatch.Elapsed); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_filtered_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key2: 2 8 | }, 9 | NewItems: [ 10 | { 11 | Key: Key2, 12 | Value: 2 13 | } 14 | ] 15 | } 16 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_filtered_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | } 14 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_filtered_set.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | } 14 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_projected_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key1: 1 8 | }, 9 | NewItems: [ 10 | { 11 | Key: Key1, 12 | Value: 1 13 | } 14 | ] 15 | }, 16 | { 17 | Current: { 18 | Key1: 1, 19 | Key2: 2 20 | }, 21 | NewItems: [ 22 | { 23 | Key: Key2, 24 | Value: 2 25 | } 26 | ] 27 | }, 28 | { 29 | Current: { 30 | Key3: 3, 31 | Key1: 1, 32 | Key2: 2 33 | }, 34 | NewItems: [ 35 | { 36 | Key: Key3, 37 | Value: 3 38 | } 39 | ] 40 | } 41 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_projected_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1 9 | ], 10 | NewItems: [ 11 | 1 12 | ] 13 | }, 14 | { 15 | Index: 1, 16 | Current: [ 17 | 1, 18 | 2 19 | ], 20 | NewItems: [ 21 | 2 22 | ] 23 | }, 24 | { 25 | Index: 2, 26 | Current: [ 27 | 1, 28 | 2, 29 | 3 30 | ], 31 | NewItems: [ 32 | 3 33 | ] 34 | } 35 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_projected_set.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1 9 | ], 10 | NewItems: [ 11 | 1 12 | ] 13 | }, 14 | { 15 | Index: 1, 16 | Current: [ 17 | 1, 18 | 2 19 | ], 20 | NewItems: [ 21 | 2 22 | ] 23 | }, 24 | { 25 | Index: 2, 26 | Current: [ 27 | 1, 28 | 2, 29 | 3 30 | ], 31 | NewItems: [ 32 | 3 33 | ] 34 | } 35 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_setSorted_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | { 9 | Key: Key3, 10 | Value: 3 11 | } 12 | ], 13 | NewItems: [ 14 | { 15 | Key: Key3, 16 | Value: 3 17 | } 18 | ] 19 | }, 20 | { 21 | Index: 0, 22 | Current: [ 23 | { 24 | Key: Key1, 25 | Value: 1 26 | }, 27 | { 28 | Key: Key3, 29 | Value: 3 30 | } 31 | ], 32 | NewItems: [ 33 | { 34 | Key: Key1, 35 | Value: 1 36 | } 37 | ] 38 | }, 39 | { 40 | Index: 1, 41 | Current: [ 42 | { 43 | Key: Key1, 44 | Value: 1 45 | }, 46 | { 47 | Key: Key2, 48 | Value: 2 49 | }, 50 | { 51 | Key: Key3, 52 | Value: 3 53 | } 54 | ], 55 | NewItems: [ 56 | { 57 | Key: Key2, 58 | Value: 2 59 | } 60 | ] 61 | } 62 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_setSorted_dictionary_with_duplicate_value.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | { 9 | Key: Key3, 10 | Value: 3 11 | } 12 | ], 13 | NewItems: [ 14 | { 15 | Key: Key3, 16 | Value: 3 17 | } 18 | ] 19 | }, 20 | { 21 | Index: 0, 22 | Current: [ 23 | { 24 | Key: Key1, 25 | Value: 1 26 | }, 27 | { 28 | Key: Key3, 29 | Value: 3 30 | } 31 | ], 32 | NewItems: [ 33 | { 34 | Key: Key1, 35 | Value: 1 36 | } 37 | ] 38 | }, 39 | { 40 | Index: 1, 41 | Current: [ 42 | { 43 | Key: Key1, 44 | Value: 1 45 | }, 46 | { 47 | Key: Key2, 48 | Value: 2 49 | }, 50 | { 51 | Key: Key3, 52 | Value: 3 53 | } 54 | ], 55 | NewItems: [ 56 | { 57 | Key: Key2, 58 | Value: 2 59 | } 60 | ] 61 | } 62 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_sorted_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | { 11 | Key: Key3, 12 | Value: 3 13 | } 14 | ], 15 | NewItems: [ 16 | { 17 | Key: Key3, 18 | Value: 3 19 | } 20 | ] 21 | }, 22 | { 23 | Index: 0, 24 | Comparer: {}, 25 | Current: [ 26 | { 27 | Key: Key1, 28 | Value: 1 29 | }, 30 | { 31 | Key: Key3, 32 | Value: 3 33 | } 34 | ], 35 | NewItems: [ 36 | { 37 | Key: Key1, 38 | Value: 1 39 | } 40 | ] 41 | }, 42 | { 43 | Index: 1, 44 | Comparer: {}, 45 | Current: [ 46 | { 47 | Key: Key1, 48 | Value: 1 49 | }, 50 | { 51 | Key: Key2, 52 | Value: 2 53 | }, 54 | { 55 | Key: Key3, 56 | Value: 3 57 | } 58 | ], 59 | NewItems: [ 60 | { 61 | Key: Key2, 62 | Value: 2 63 | } 64 | ] 65 | } 66 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_sorted_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 3 11 | ], 12 | NewItems: [ 13 | 3 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 3 22 | ], 23 | NewItems: [ 24 | 1 25 | ] 26 | }, 27 | { 28 | Index: 1, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 2 37 | ] 38 | } 39 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Add_to_sorted_list_with_Changes.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 3 11 | ], 12 | NewItems: [ 13 | 3 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 3 22 | ], 23 | NewItems: [ 24 | 1 25 | ] 26 | }, 27 | { 28 | Index: 1, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 2 37 | ] 38 | } 39 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Concat_Add.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1 9 | ], 10 | NewItems: [ 11 | 1 12 | ] 13 | }, 14 | { 15 | Index: 1, 16 | Current: [ 17 | 1, 18 | 2 19 | ], 20 | NewItems: [ 21 | 2 22 | ] 23 | } 24 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Concat_Add_Delayed.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Current: [ 4 | 1, 5 | 2 6 | ], 7 | Action: Reset 8 | } 9 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Concat_Add_Delayed2.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Current: [ 4 | 0, 5 | 1, 6 | 2, 7 | 3, 8 | 4, 9 | 5, 10 | 6 11 | ], 12 | Action: Reset 13 | } 14 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Concat_Clear.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1 9 | ], 10 | NewItems: [ 11 | 1 12 | ] 13 | }, 14 | { 15 | Index: 1, 16 | Current: [ 17 | 1, 18 | 2 19 | ], 20 | NewItems: [ 21 | 2 22 | ] 23 | }, 24 | { 25 | Index: 0, 26 | Current: [ 27 | 2 28 | ], 29 | OldItems: [ 30 | 1 31 | ], 32 | Action: Remove 33 | }, 34 | { 35 | Index: 0, 36 | OldItems: [ 37 | 2 38 | ], 39 | Action: Remove 40 | } 41 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Concat_Remove.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1 9 | ], 10 | NewItems: [ 11 | 1 12 | ] 13 | }, 14 | { 15 | Index: 1, 16 | Current: [ 17 | 1, 18 | 2 19 | ], 20 | NewItems: [ 21 | 2 22 | ] 23 | }, 24 | { 25 | Index: 0, 26 | Current: [ 27 | 2 28 | ], 29 | OldItems: [ 30 | 1 31 | ], 32 | Action: Remove 33 | }, 34 | { 35 | Index: 0, 36 | OldItems: [ 37 | 2 38 | ], 39 | Action: Remove 40 | } 41 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Concat_Replace_multiple.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1, 9 | 2, 10 | 3 11 | ], 12 | NewItems: [ 13 | 1, 14 | 2, 15 | 3 16 | ] 17 | }, 18 | { 19 | Index: 3, 20 | Current: [ 21 | 1, 22 | 2, 23 | 3, 24 | 4, 25 | 5, 26 | 6 27 | ], 28 | NewItems: [ 29 | 4, 30 | 5, 31 | 6 32 | ] 33 | }, 34 | { 35 | Index: 1, 36 | Current: [ 37 | 1, 38 | -2, 39 | -3, 40 | 4, 41 | 5, 42 | 6 43 | ], 44 | OldItems: [ 45 | 2, 46 | 3 47 | ], 48 | NewItems: [ 49 | -2, 50 | -3 51 | ], 52 | Action: Replace 53 | }, 54 | { 55 | Index: 4, 56 | Current: [ 57 | 1, 58 | -2, 59 | -3, 60 | 4, 61 | -5, 62 | -6 63 | ], 64 | OldItems: [ 65 | 5, 66 | 6 67 | ], 68 | NewItems: [ 69 | -5, 70 | -6 71 | ], 72 | Action: Replace 73 | } 74 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Concat_Replace_single.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1, 9 | 2, 10 | 3 11 | ], 12 | NewItems: [ 13 | 1, 14 | 2, 15 | 3 16 | ] 17 | }, 18 | { 19 | Index: 3, 20 | Current: [ 21 | 1, 22 | 2, 23 | 3, 24 | 4, 25 | 5, 26 | 6 27 | ], 28 | NewItems: [ 29 | 4, 30 | 5, 31 | 6 32 | ] 33 | }, 34 | { 35 | Index: 1, 36 | Current: [ 37 | 1, 38 | -2, 39 | 3, 40 | 4, 41 | 5, 42 | 6 43 | ], 44 | OldItems: [ 45 | 2 46 | ], 47 | NewItems: [ 48 | -2 49 | ], 50 | Action: Replace 51 | }, 52 | { 53 | Index: 4, 54 | Current: [ 55 | 1, 56 | -2, 57 | 3, 58 | 4, 59 | -5, 60 | 6 61 | ], 62 | OldItems: [ 63 | 5 64 | ], 65 | NewItems: [ 66 | -5 67 | ], 68 | Action: Replace 69 | } 70 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.List_ToObservableCollection_Add.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 1 5 | ], 6 | OldStartingIndex: -1 7 | }, 8 | { 9 | NewItems: [ 10 | 2 11 | ], 12 | NewStartingIndex: 1, 13 | OldStartingIndex: -1 14 | }, 15 | { 16 | NewItems: [ 17 | 3 18 | ], 19 | NewStartingIndex: 2, 20 | OldStartingIndex: -1 21 | } 22 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.List_ToObservableCollection_Clear.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 1, 5 | 2, 6 | 3, 7 | 4, 8 | 5 9 | ], 10 | OldStartingIndex: -1 11 | }, 12 | { 13 | Action: Reset, 14 | NewStartingIndex: -1, 15 | OldStartingIndex: -1 16 | }, 17 | { 18 | NewItems: [ 19 | 1, 20 | 2 21 | ], 22 | OldStartingIndex: -1 23 | } 24 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.List_ToObservableCollection_Remove.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 1 5 | ], 6 | OldStartingIndex: -1 7 | }, 8 | { 9 | NewItems: [ 10 | 2 11 | ], 12 | NewStartingIndex: 1, 13 | OldStartingIndex: -1 14 | }, 15 | { 16 | Action: Remove, 17 | OldItems: [ 18 | 1 19 | ], 20 | NewStartingIndex: -1 21 | } 22 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.List_ToObservableCollection_Replace.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 1 5 | ], 6 | OldStartingIndex: -1 7 | }, 8 | { 9 | NewItems: [ 10 | 2 11 | ], 12 | NewStartingIndex: 1, 13 | OldStartingIndex: -1 14 | }, 15 | { 16 | Action: Replace, 17 | NewItems: [ 18 | 3 19 | ], 20 | OldItems: [ 21 | 1 22 | ] 23 | } 24 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.List_ToObservableCollection_does_not_raise_events_on_event_subscription.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 4 5 | ], 6 | NewStartingIndex: 3, 7 | OldStartingIndex: -1 8 | } 9 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_from_filtered_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key2: 2 8 | }, 9 | NewItems: [ 10 | { 11 | Key: Key2, 12 | Value: 2 13 | } 14 | ] 15 | }, 16 | { 17 | OldItems: [ 18 | { 19 | Key: Key2, 20 | Value: 2 21 | } 22 | ], 23 | Action: Remove 24 | } 25 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_from_filtered_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | OldItems: [ 17 | 2 18 | ], 19 | Action: Remove 20 | } 21 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_from_filtered_set.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | OldItems: [ 17 | 2 18 | ], 19 | Action: Remove 20 | } 21 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_from_projected_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key1: 1 8 | }, 9 | NewItems: [ 10 | { 11 | Key: Key1, 12 | Value: 1 13 | } 14 | ] 15 | }, 16 | { 17 | Current: { 18 | Key1: 1, 19 | Key2: 2 20 | }, 21 | NewItems: [ 22 | { 23 | Key: Key2, 24 | Value: 2 25 | } 26 | ] 27 | }, 28 | { 29 | Current: { 30 | Key3: 3, 31 | Key1: 1, 32 | Key2: 2 33 | }, 34 | NewItems: [ 35 | { 36 | Key: Key3, 37 | Value: 3 38 | } 39 | ] 40 | }, 41 | { 42 | Current: { 43 | Key3: 3, 44 | Key1: 1 45 | }, 46 | OldItems: [ 47 | { 48 | Key: Key2, 49 | Value: 2 50 | } 51 | ], 52 | Action: Remove 53 | } 54 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_from_projected_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1, 9 | 2, 10 | 3 11 | ], 12 | NewItems: [ 13 | 1, 14 | 2, 15 | 3 16 | ] 17 | }, 18 | { 19 | Index: 1, 20 | Current: [ 21 | 1, 22 | 3 23 | ], 24 | OldItems: [ 25 | 2 26 | ], 27 | Action: Remove 28 | } 29 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_from_projected_set.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1 9 | ], 10 | NewItems: [ 11 | 1 12 | ] 13 | }, 14 | { 15 | Index: 1, 16 | Current: [ 17 | 1, 18 | 2 19 | ], 20 | NewItems: [ 21 | 2 22 | ] 23 | }, 24 | { 25 | Index: 2, 26 | Current: [ 27 | 1, 28 | 2, 29 | 3 30 | ], 31 | NewItems: [ 32 | 3 33 | ] 34 | }, 35 | { 36 | Index: 1, 37 | Current: [ 38 | 1, 39 | 3 40 | ], 41 | OldItems: [ 42 | 2 43 | ], 44 | Action: Remove 45 | } 46 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_from_sorted_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 3 11 | ], 12 | NewItems: [ 13 | 3 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | Comparer: {}, 19 | Current: [ 20 | 2, 21 | 3 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 0, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 1 37 | ] 38 | }, 39 | { 40 | Index: 1, 41 | Comparer: {}, 42 | Current: [ 43 | 1, 44 | 3 45 | ], 46 | OldItems: [ 47 | 2 48 | ], 49 | Action: Remove 50 | } 51 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_from_sorted_list_with_Changes.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 3 11 | ], 12 | NewItems: [ 13 | 3 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | Comparer: {}, 19 | Current: [ 20 | 2, 21 | 3 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 0, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 1 37 | ] 38 | }, 39 | { 40 | Index: 1, 41 | Comparer: {}, 42 | Current: [ 43 | 1, 44 | 3 45 | ], 46 | OldItems: [ 47 | 2 48 | ], 49 | Action: Remove 50 | } 51 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_in_setSorted_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | { 9 | Key: Key1, 10 | Value: 1 11 | } 12 | ], 13 | NewItems: [ 14 | { 15 | Key: Key1, 16 | Value: 1 17 | } 18 | ] 19 | }, 20 | { 21 | Index: 1, 22 | Current: [ 23 | { 24 | Key: Key1, 25 | Value: 1 26 | }, 27 | { 28 | Key: Key2, 29 | Value: 2 30 | } 31 | ], 32 | NewItems: [ 33 | { 34 | Key: Key2, 35 | Value: 2 36 | } 37 | ] 38 | }, 39 | { 40 | Index: 0, 41 | Current: [ 42 | { 43 | Key: Key2, 44 | Value: 2 45 | } 46 | ], 47 | OldItems: [ 48 | { 49 | Key: Key1, 50 | Value: 1 51 | } 52 | ], 53 | Action: Remove 54 | } 55 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Remove_in_sorted_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | { 11 | Key: Key1, 12 | Value: 1 13 | } 14 | ], 15 | NewItems: [ 16 | { 17 | Key: Key1, 18 | Value: 1 19 | } 20 | ] 21 | }, 22 | { 23 | Index: 1, 24 | Comparer: {}, 25 | Current: [ 26 | { 27 | Key: Key1, 28 | Value: 1 29 | }, 30 | { 31 | Key: Key2, 32 | Value: 2 33 | } 34 | ], 35 | NewItems: [ 36 | { 37 | Key: Key2, 38 | Value: 2 39 | } 40 | ] 41 | }, 42 | { 43 | Index: 0, 44 | Comparer: {}, 45 | Current: [ 46 | { 47 | Key: Key2, 48 | Value: 2 49 | } 50 | ], 51 | OldItems: [ 52 | { 53 | Key: Key1, 54 | Value: 1 55 | } 56 | ], 57 | Action: Remove 58 | } 59 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_filtered_dictionary_addition.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key2: 2 8 | }, 9 | NewItems: [ 10 | { 11 | Key: Key2, 12 | Value: 2 13 | } 14 | ] 15 | }, 16 | { 17 | Current: { 18 | Key1: 4, 19 | Key2: 2 20 | }, 21 | NewItems: [ 22 | { 23 | Key: Key1, 24 | Value: 4 25 | } 26 | ] 27 | } 28 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_filtered_dictionary_removal.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key2: 2 8 | }, 9 | NewItems: [ 10 | { 11 | Key: Key2, 12 | Value: 2 13 | } 14 | ] 15 | }, 16 | { 17 | OldItems: [ 18 | { 19 | Key: Key2, 20 | Value: 2 21 | } 22 | ], 23 | Action: Remove 24 | } 25 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_filtered_dictionary_replacement.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key2: 2 8 | }, 9 | NewItems: [ 10 | { 11 | Key: Key2, 12 | Value: 2 13 | } 14 | ] 15 | }, 16 | { 17 | OldItems: [ 18 | { 19 | Key: Key2, 20 | Value: 2 21 | } 22 | ], 23 | Action: Remove 24 | }, 25 | { 26 | Current: { 27 | Key2: 4 28 | }, 29 | NewItems: [ 30 | { 31 | Key: Key2, 32 | Value: 4 33 | } 34 | ] 35 | } 36 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_filtered_from_ListChangedNotification_observable.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | OldItems: [ 17 | 2 18 | ], 19 | Action: Remove 20 | }, 21 | { 22 | Index: 0, 23 | Current: [ 24 | 4 25 | ], 26 | NewItems: [ 27 | 4 28 | ] 29 | } 30 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_filtered_list_addition.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | }, 14 | { 15 | Index: 1, 16 | Current: [ 17 | 2, 18 | 4 19 | ], 20 | NewItems: [ 21 | 4 22 | ] 23 | } 24 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_filtered_list_removal.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | OldItems: [ 17 | 2 18 | ], 19 | Action: Remove 20 | } 21 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_filtered_list_replacement.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | Current: [ 17 | 4 18 | ], 19 | OldItems: [ 20 | 2 21 | ], 22 | NewItems: [ 23 | 4 24 | ], 25 | Action: Replace 26 | } 27 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_filtered_list_replacement2.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 4 9 | ], 10 | NewItems: [ 11 | 4 12 | ] 13 | } 14 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_projected_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | Key1: 1 8 | }, 9 | NewItems: [ 10 | { 11 | Key: Key1, 12 | Value: 1 13 | } 14 | ] 15 | }, 16 | { 17 | Current: { 18 | Key1: 1, 19 | Key2: 2 20 | }, 21 | NewItems: [ 22 | { 23 | Key: Key2, 24 | Value: 2 25 | } 26 | ] 27 | }, 28 | { 29 | Current: { 30 | Key3: 3, 31 | Key1: 1, 32 | Key2: 2 33 | }, 34 | NewItems: [ 35 | { 36 | Key: Key3, 37 | Value: 3 38 | } 39 | ] 40 | }, 41 | { 42 | Current: { 43 | Key3: 3, 44 | Key1: 1 45 | }, 46 | OldItems: [ 47 | { 48 | Key: Key2, 49 | Value: 2 50 | } 51 | ], 52 | Action: Remove 53 | }, 54 | { 55 | Current: { 56 | Key3: 3, 57 | Key1: 1, 58 | Key2: 4 59 | }, 60 | NewItems: [ 61 | { 62 | Key: Key2, 63 | Value: 4 64 | } 65 | ] 66 | } 67 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_projected_from_ListChangedNotification_observable.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1, 9 | 2 10 | ], 11 | NewItems: [ 12 | 1, 13 | 2 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | OldItems: [ 19 | 1, 20 | 2 21 | ], 22 | Action: Remove 23 | }, 24 | { 25 | Index: 0, 26 | Current: [ 27 | 3, 28 | 4 29 | ], 30 | NewItems: [ 31 | 3, 32 | 4 33 | ] 34 | } 35 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_projected_from_SortedSetChangedNotification_observable.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1, 9 | 2 10 | ], 11 | NewItems: [ 12 | 1, 13 | 2 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | OldItems: [ 19 | 1, 20 | 2 21 | ], 22 | Action: Remove 23 | }, 24 | { 25 | Index: 0, 26 | Current: [ 27 | 3, 28 | 4 29 | ], 30 | NewItems: [ 31 | 3, 32 | 4 33 | ] 34 | } 35 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_projected_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1, 9 | 2, 10 | 3 11 | ], 12 | NewItems: [ 13 | 1, 14 | 2, 15 | 3 16 | ] 17 | }, 18 | { 19 | Index: 1, 20 | Current: [ 21 | 1, 22 | 4, 23 | 3 24 | ], 25 | OldItems: [ 26 | 2 27 | ], 28 | NewItems: [ 29 | 4 30 | ], 31 | Action: Replace 32 | } 33 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_setSorted_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | { 9 | Key: Key1, 10 | Value: 3 11 | } 12 | ], 13 | NewItems: [ 14 | { 15 | Key: Key1, 16 | Value: 3 17 | } 18 | ] 19 | }, 20 | { 21 | Index: 0, 22 | Current: [ 23 | { 24 | Key: Key2, 25 | Value: 1 26 | }, 27 | { 28 | Key: Key1, 29 | Value: 3 30 | } 31 | ], 32 | NewItems: [ 33 | { 34 | Key: Key2, 35 | Value: 1 36 | } 37 | ] 38 | }, 39 | { 40 | Index: 2, 41 | Current: [ 42 | { 43 | Key: Key2, 44 | Value: 1 45 | }, 46 | { 47 | Key: Key1, 48 | Value: 3 49 | }, 50 | { 51 | Key: Key3, 52 | Value: 4 53 | } 54 | ], 55 | NewItems: [ 56 | { 57 | Key: Key3, 58 | Value: 4 59 | } 60 | ] 61 | }, 62 | { 63 | Index: 0, 64 | Current: [ 65 | { 66 | Key: Key1, 67 | Value: 3 68 | }, 69 | { 70 | Key: Key3, 71 | Value: 4 72 | } 73 | ], 74 | OldItems: [ 75 | { 76 | Key: Key2, 77 | Value: 1 78 | } 79 | ], 80 | Action: Remove 81 | }, 82 | { 83 | Index: 0, 84 | Current: [ 85 | { 86 | Key: Key2, 87 | Value: 2 88 | }, 89 | { 90 | Key: Key1, 91 | Value: 3 92 | }, 93 | { 94 | Key: Key3, 95 | Value: 4 96 | } 97 | ], 98 | NewItems: [ 99 | { 100 | Key: Key2, 101 | Value: 2 102 | } 103 | ] 104 | } 105 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_setSorted_projected_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 30 9 | ], 10 | NewItems: [ 11 | 30 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | Current: [ 17 | 20, 18 | 30 19 | ], 20 | NewItems: [ 21 | 20 22 | ] 23 | }, 24 | { 25 | Index: 0, 26 | Current: [ 27 | 10, 28 | 20, 29 | 30 30 | ], 31 | NewItems: [ 32 | 10 33 | ] 34 | }, 35 | { 36 | Index: 1, 37 | Current: [ 38 | 10, 39 | 30 40 | ], 41 | OldItems: [ 42 | 20 43 | ], 44 | Action: Remove 45 | }, 46 | { 47 | Index: 2, 48 | Current: [ 49 | 10, 50 | 30, 51 | 40 52 | ], 53 | NewItems: [ 54 | 40 55 | ] 56 | } 57 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_setSorted_projected_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 3 9 | ], 10 | NewItems: [ 11 | 3 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | Current: [ 17 | 2, 18 | 3 19 | ], 20 | NewItems: [ 21 | 2 22 | ] 23 | }, 24 | { 25 | Index: 0, 26 | Current: [ 27 | 1, 28 | 2, 29 | 3 30 | ], 31 | NewItems: [ 32 | 1 33 | ] 34 | }, 35 | { 36 | Index: 1, 37 | Current: [ 38 | 1, 39 | 3 40 | ], 41 | OldItems: [ 42 | 2 43 | ], 44 | Action: Remove 45 | }, 46 | { 47 | Index: 2, 48 | Current: [ 49 | 1, 50 | 3, 51 | 4 52 | ], 53 | NewItems: [ 54 | 4 55 | ] 56 | } 57 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_sorted_dictionary.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | { 11 | Key: Key1, 12 | Value: 3 13 | } 14 | ], 15 | NewItems: [ 16 | { 17 | Key: Key1, 18 | Value: 3 19 | } 20 | ] 21 | }, 22 | { 23 | Index: 0, 24 | Comparer: {}, 25 | Current: [ 26 | { 27 | Key: Key2, 28 | Value: 1 29 | }, 30 | { 31 | Key: Key1, 32 | Value: 3 33 | } 34 | ], 35 | NewItems: [ 36 | { 37 | Key: Key2, 38 | Value: 1 39 | } 40 | ] 41 | }, 42 | { 43 | Index: 2, 44 | Comparer: {}, 45 | Current: [ 46 | { 47 | Key: Key2, 48 | Value: 1 49 | }, 50 | { 51 | Key: Key1, 52 | Value: 3 53 | }, 54 | { 55 | Key: Key3, 56 | Value: 4 57 | } 58 | ], 59 | NewItems: [ 60 | { 61 | Key: Key3, 62 | Value: 4 63 | } 64 | ] 65 | }, 66 | { 67 | Index: 0, 68 | Comparer: {}, 69 | Current: [ 70 | { 71 | Key: Key1, 72 | Value: 3 73 | }, 74 | { 75 | Key: Key3, 76 | Value: 4 77 | } 78 | ], 79 | OldItems: [ 80 | { 81 | Key: Key2, 82 | Value: 1 83 | } 84 | ], 85 | Action: Remove 86 | }, 87 | { 88 | Index: 0, 89 | Comparer: {}, 90 | Current: [ 91 | { 92 | Key: Key2, 93 | Value: 2 94 | }, 95 | { 96 | Key: Key1, 97 | Value: 3 98 | }, 99 | { 100 | Key: Key3, 101 | Value: 4 102 | } 103 | ], 104 | NewItems: [ 105 | { 106 | Key: Key2, 107 | Value: 2 108 | } 109 | ] 110 | } 111 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_sorted_from_ListChangedNotification_observable.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | }, 16 | { 17 | Index: 1, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 2 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 0, 29 | Comparer: {}, 30 | Current: [ 31 | 2 32 | ], 33 | OldItems: [ 34 | 1 35 | ], 36 | Action: Remove 37 | }, 38 | { 39 | Index: 0, 40 | Comparer: {}, 41 | OldItems: [ 42 | 2 43 | ], 44 | Action: Remove 45 | }, 46 | { 47 | Index: 0, 48 | Comparer: {}, 49 | Current: [ 50 | 3 51 | ], 52 | NewItems: [ 53 | 3 54 | ] 55 | }, 56 | { 57 | Index: 1, 58 | Comparer: {}, 59 | Current: [ 60 | 3, 61 | 4 62 | ], 63 | NewItems: [ 64 | 4 65 | ] 66 | } 67 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_sorted_list.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 3 11 | ], 12 | NewItems: [ 13 | 3 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | Comparer: {}, 19 | Current: [ 20 | 2, 21 | 3 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 0, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 1 37 | ] 38 | }, 39 | { 40 | Index: 1, 41 | Comparer: {}, 42 | Current: [ 43 | 1, 44 | 3 45 | ], 46 | OldItems: [ 47 | 2 48 | ], 49 | Action: Remove 50 | }, 51 | { 52 | Index: 2, 53 | Comparer: {}, 54 | Current: [ 55 | 1, 56 | 3, 57 | 4 58 | ], 59 | NewItems: [ 60 | 4 61 | ] 62 | } 63 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Replace_in_sorted_list_with_Changes.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 3 11 | ], 12 | NewItems: [ 13 | 3 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | Comparer: {}, 19 | Current: [ 20 | 2, 21 | 3 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 0, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 1 37 | ] 38 | }, 39 | { 40 | Index: 1, 41 | Comparer: {}, 42 | Current: [ 43 | 1, 44 | 3 45 | ], 46 | OldItems: [ 47 | 2 48 | ], 49 | Action: Remove 50 | }, 51 | { 52 | Index: 2, 53 | Comparer: {}, 54 | Current: [ 55 | 1, 56 | 3, 57 | 4 58 | ], 59 | NewItems: [ 60 | 4 61 | ] 62 | } 63 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Select_after_Select_on_dictionaries_behaves_correctly.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | 1: 36! 8 | }, 9 | NewItems: [ 10 | { 11 | Key: 1, 12 | Value: 36! 13 | } 14 | ] 15 | }, 16 | { 17 | Current: { 18 | 1: 36!, 19 | 2: 37! 20 | }, 21 | NewItems: [ 22 | { 23 | Key: 2, 24 | Value: 37! 25 | } 26 | ] 27 | }, 28 | { 29 | Current: { 30 | 1: 36! 31 | }, 32 | OldItems: [ 33 | { 34 | Key: 2, 35 | Value: 37! 36 | } 37 | ], 38 | Action: Remove 39 | }, 40 | { 41 | OldItems: [ 42 | { 43 | Key: 1, 44 | Value: 36! 45 | } 46 | ], 47 | Action: Remove 48 | }, 49 | { 50 | Current: { 51 | 4: 38! 52 | }, 53 | NewItems: [ 54 | { 55 | Key: 4, 56 | Value: 38! 57 | } 58 | ] 59 | } 60 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Select_after_SortSet_preserves_order.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 36 9 | ], 10 | NewItems: [ 11 | 36 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | Current: [ 17 | 35, 18 | 36 19 | ], 20 | NewItems: [ 21 | 35 22 | ] 23 | }, 24 | { 25 | Index: 0, 26 | Current: [ 27 | 34, 28 | 35, 29 | 36 30 | ], 31 | NewItems: [ 32 | 34 33 | ] 34 | }, 35 | { 36 | Index: 3, 37 | Current: [ 38 | 34, 39 | 35, 40 | 36, 41 | 37 42 | ], 43 | NewItems: [ 44 | 37 45 | ] 46 | } 47 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Select_after_Where_behaves_correctly.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 2 9 | ], 10 | NewItems: [ 11 | 2 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | OldItems: [ 17 | 2 18 | ], 19 | Action: Remove 20 | }, 21 | { 22 | Index: 0, 23 | Current: [ 24 | 4 25 | ], 26 | NewItems: [ 27 | 4 28 | ] 29 | }, 30 | { 31 | Index: 1, 32 | Current: [ 33 | 4, 34 | 2 35 | ], 36 | NewItems: [ 37 | 2 38 | ] 39 | }, 40 | { 41 | Index: 1, 42 | Current: [ 43 | 4 44 | ], 45 | OldItems: [ 46 | 2 47 | ], 48 | Action: Remove 49 | } 50 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Select_after_Where_on_dictionaries_behaves_correctly.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | 1: 36 8 | }, 9 | NewItems: [ 10 | { 11 | Key: 1, 12 | Value: 36 13 | } 14 | ] 15 | }, 16 | { 17 | OldItems: [ 18 | { 19 | Key: 1, 20 | Value: 36 21 | } 22 | ], 23 | Action: Remove 24 | }, 25 | { 26 | Current: { 27 | 4: 38 28 | }, 29 | NewItems: [ 30 | { 31 | Key: 4, 32 | Value: 38 33 | } 34 | ] 35 | }, 36 | { 37 | OldItems: [ 38 | { 39 | Key: 4, 40 | Value: 38 41 | } 42 | ], 43 | Action: Remove 44 | } 45 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.SortedSet_ToObservableCollection_Add.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 1 5 | ], 6 | OldStartingIndex: -1 7 | }, 8 | { 9 | NewItems: [ 10 | 2 11 | ], 12 | NewStartingIndex: 1, 13 | OldStartingIndex: -1 14 | }, 15 | { 16 | NewItems: [ 17 | 3 18 | ], 19 | NewStartingIndex: 2, 20 | OldStartingIndex: -1 21 | } 22 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.SortedSet_ToObservableCollection_Clear.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 1, 5 | 2, 6 | 3, 7 | 4, 8 | 5 9 | ], 10 | OldStartingIndex: -1 11 | }, 12 | { 13 | Action: Reset, 14 | NewStartingIndex: -1, 15 | OldStartingIndex: -1 16 | }, 17 | { 18 | NewItems: [ 19 | 1, 20 | 2 21 | ], 22 | OldStartingIndex: -1 23 | } 24 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.SortedSet_ToObservableCollection_Remove.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 1 5 | ], 6 | OldStartingIndex: -1 7 | }, 8 | { 9 | NewItems: [ 10 | 2 11 | ], 12 | NewStartingIndex: 1, 13 | OldStartingIndex: -1 14 | }, 15 | { 16 | Action: Remove, 17 | OldItems: [ 18 | 1 19 | ], 20 | NewStartingIndex: -1 21 | } 22 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.SortedSet_ToObservableCollection_does_not_raise_events_on_event_subscription.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | NewItems: [ 4 | 4 5 | ], 6 | NewStartingIndex: 3, 7 | OldStartingIndex: -1 8 | } 9 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Where_after_Where_behaves_correctly.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 6 9 | ], 10 | NewItems: [ 11 | 6 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | OldItems: [ 17 | 6 18 | ], 19 | Action: Remove 20 | } 21 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/ReactiveCollectionExtensionsTest.Where_after_Where_on_dictionaries_behaves_correctly.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Current: { 7 | 4: 6 8 | }, 9 | NewItems: [ 10 | { 11 | Key: 4, 12 | Value: 6 13 | } 14 | ] 15 | }, 16 | { 17 | OldItems: [ 18 | { 19 | Key: 4, 20 | Value: 6 21 | } 22 | ], 23 | Action: Remove 24 | } 25 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.Add.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | } 16 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.AddRange.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 3 11 | ], 12 | NewItems: [ 13 | 3 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 3 22 | ], 23 | NewItems: [ 24 | 1 25 | ] 26 | }, 27 | { 28 | Index: 1, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 2 37 | ] 38 | } 39 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.Clear.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | }, 16 | { 17 | Comparer: {}, 18 | Action: Reset 19 | } 20 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.First_notification_is_reset.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | } 6 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.Remove.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | }, 16 | { 17 | Index: 0, 18 | Comparer: {}, 19 | OldItems: [ 20 | 1 21 | ], 22 | Action: Remove 23 | } 24 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.RemoveAll.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | }, 16 | { 17 | Index: 1, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 2 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 2, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 3 37 | ] 38 | }, 39 | { 40 | Comparer: {}, 41 | Current: [ 42 | 1, 43 | 3 44 | ], 45 | Action: Reset 46 | } 47 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.RemoveAt.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | }, 16 | { 17 | Index: 1, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 2 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 1, 29 | Comparer: {}, 30 | Current: [ 31 | 1 32 | ], 33 | OldItems: [ 34 | 2 35 | ], 36 | Action: Remove 37 | } 38 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.RemoveRange.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | }, 16 | { 17 | Index: 1, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 2 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 2, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 3 37 | ] 38 | }, 39 | { 40 | Index: 3, 41 | Comparer: {}, 42 | Current: [ 43 | 1, 44 | 2, 45 | 3, 46 | 4 47 | ], 48 | NewItems: [ 49 | 4 50 | ] 51 | }, 52 | { 53 | Comparer: {}, 54 | Current: [ 55 | 1, 56 | 3 57 | ], 58 | OldItems: [ 59 | 2, 60 | 4 61 | ], 62 | Action: Remove 63 | } 64 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.RemoveRange2.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | }, 16 | { 17 | Index: 1, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 2 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 2, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 3 37 | ] 38 | }, 39 | { 40 | Index: 3, 41 | Comparer: {}, 42 | Current: [ 43 | 1, 44 | 2, 45 | 3, 46 | 4 47 | ], 48 | NewItems: [ 49 | 4 50 | ] 51 | }, 52 | { 53 | Index: 2, 54 | Comparer: {}, 55 | Current: [ 56 | 1, 57 | 2 58 | ], 59 | OldItems: [ 60 | 3, 61 | 4 62 | ], 63 | Action: Remove 64 | } 65 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.Replace.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Comparer: {}, 4 | Action: Reset 5 | }, 6 | { 7 | Index: 0, 8 | Comparer: {}, 9 | Current: [ 10 | 1 11 | ], 12 | NewItems: [ 13 | 1 14 | ] 15 | }, 16 | { 17 | Index: 1, 18 | Comparer: {}, 19 | Current: [ 20 | 1, 21 | 2 22 | ], 23 | NewItems: [ 24 | 2 25 | ] 26 | }, 27 | { 28 | Index: 2, 29 | Comparer: {}, 30 | Current: [ 31 | 1, 32 | 2, 33 | 3 34 | ], 35 | NewItems: [ 36 | 3 37 | ] 38 | }, 39 | { 40 | Index: 3, 41 | Comparer: {}, 42 | Current: [ 43 | 1, 44 | 2, 45 | 3, 46 | 4 47 | ], 48 | NewItems: [ 49 | 4 50 | ] 51 | }, 52 | { 53 | Index: 1, 54 | Comparer: {}, 55 | Current: [ 56 | 1, 57 | 1, 58 | 2, 59 | 3, 60 | 4 61 | ], 62 | NewItems: [ 63 | 1 64 | ] 65 | }, 66 | { 67 | Index: 0, 68 | Comparer: {}, 69 | Current: [ 70 | 1, 71 | 2, 72 | 3, 73 | 4 74 | ], 75 | OldItems: [ 76 | 1 77 | ], 78 | Action: Remove 79 | }, 80 | { 81 | Index: 4, 82 | Comparer: {}, 83 | Current: [ 84 | 1, 85 | 2, 86 | 3, 87 | 4, 88 | 5 89 | ], 90 | NewItems: [ 91 | 5 92 | ] 93 | } 94 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedListReactiveCollectionSourceTest.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Linq; 2 | using System.Reactive.Threading.Tasks; 3 | using System.Threading.Tasks; 4 | using FluentAssertions; 5 | using VerifyXunit; 6 | using Xunit; 7 | 8 | namespace ExRam.ReactiveCollections.Tests 9 | { 10 | public class SortedListReactiveCollectionSourceTest : VerifyBase 11 | { 12 | public SortedListReactiveCollectionSourceTest() : base() 13 | { 14 | 15 | } 16 | 17 | [Fact] 18 | public async Task First_notification_is_reset() 19 | { 20 | var list = new SortedListReactiveCollectionSource(); 21 | 22 | var notificationsTask = await list.ReactiveCollection.Changes 23 | .Take(1) 24 | .ToArray() 25 | .ToTask(); 26 | 27 | await Verify(notificationsTask); 28 | } 29 | 30 | [Fact] 31 | public async Task Add() 32 | { 33 | var list = new SortedListReactiveCollectionSource(); 34 | 35 | var notificationsTask = list.ReactiveCollection.Changes 36 | .Take(2) 37 | .ToArray() 38 | .ToTask(); 39 | 40 | list.Add(1); 41 | 42 | await Verify(notificationsTask); 43 | } 44 | 45 | [Fact] 46 | public async Task AddRange() 47 | { 48 | var list = new SortedListReactiveCollectionSource(); 49 | 50 | var notificationsTask = list.ReactiveCollection.Changes 51 | .Take(4) 52 | .ToArray() 53 | .ToTask(); 54 | 55 | list.AddRange(new[] { 3, 1, 2 }); 56 | 57 | await Verify(notificationsTask); 58 | } 59 | 60 | [Fact] 61 | public async Task Clear() 62 | { 63 | var list = new SortedListReactiveCollectionSource(); 64 | 65 | var notificationsTask = list.ReactiveCollection.Changes 66 | .Take(3) 67 | .ToArray() 68 | .ToTask(); 69 | 70 | list.Add(1); 71 | list.Clear(); 72 | 73 | await Verify(notificationsTask); 74 | } 75 | 76 | [Fact] 77 | public void Contains() 78 | { 79 | var list = new SortedListReactiveCollectionSource 80 | { 81 | 1 82 | }; 83 | 84 | list.Contains(1).Should().BeTrue(); 85 | list.Contains(2).Should().BeFalse(); 86 | } 87 | 88 | [Fact] 89 | public void CopyTo() 90 | { 91 | var list = new SortedListReactiveCollectionSource(); 92 | 93 | var range = new[] { 1, 2, 3 }; 94 | list.AddRange(range); 95 | 96 | var target = new int[5]; 97 | list.CopyTo(target, 2); 98 | 99 | target.Should().Equal(0, 0, 1, 2, 3); 100 | } 101 | 102 | [Fact] 103 | public void GetEnumerator() 104 | { 105 | var list = new SortedListReactiveCollectionSource(); 106 | 107 | var range = new[] { 1, 2, 3 }; 108 | list.AddRange(range); 109 | 110 | using (var enumerator = list.GetEnumerator()) 111 | { 112 | enumerator.MoveNext().Should().BeTrue(); 113 | enumerator.Current.Should().Be(1); 114 | enumerator.MoveNext().Should().BeTrue(); 115 | enumerator.Current.Should().Be(2); 116 | enumerator.MoveNext().Should().BeTrue(); 117 | enumerator.Current.Should().Be(3); 118 | enumerator.MoveNext().Should().BeFalse(); 119 | } 120 | } 121 | 122 | [Fact] 123 | public void IndexOf() 124 | { 125 | var list = new SortedListReactiveCollectionSource(); 126 | 127 | list.AddRange(new[] { 1, 2, 3 }); 128 | 129 | list.IndexOf(3).Should().Be(2); 130 | list.IndexOf(2).Should().Be(1); 131 | list.IndexOf(1).Should().Be(0); 132 | } 133 | 134 | [Fact] 135 | public void Item() 136 | { 137 | var list = new SortedListReactiveCollectionSource(); 138 | list.Add(1); 139 | 140 | list[0].Should().Be(1); 141 | } 142 | 143 | [Fact] 144 | public async Task Remove() 145 | { 146 | var list = new SortedListReactiveCollectionSource(); 147 | 148 | var notificationsTask = list.ReactiveCollection.Changes 149 | .Take(3) 150 | .ToArray() 151 | .ToTask(); 152 | 153 | list.Add(1); 154 | list.Remove(1); 155 | 156 | await Verify(notificationsTask); 157 | } 158 | 159 | [Fact] 160 | public async Task RemoveAll() 161 | { 162 | var list = new SortedListReactiveCollectionSource(); 163 | 164 | var notificationsTask = list.ReactiveCollection.Changes 165 | .Take(5) 166 | .ToArray() 167 | .ToTask(); 168 | 169 | list.AddRange(new[] { 1, 2, 3 }); 170 | list.RemoveAll(x => x % 2 == 0); 171 | 172 | await Verify(notificationsTask); 173 | } 174 | 175 | [Fact] 176 | public async Task RemoveAt() 177 | { 178 | var list = new SortedListReactiveCollectionSource(); 179 | 180 | var notificationsTask = list.ReactiveCollection.Changes 181 | .Take(4) 182 | .ToArray() 183 | .FirstAsync() 184 | .ToTask(); 185 | 186 | list.Add(1); 187 | list.Add(2); 188 | list.RemoveAt(1); 189 | 190 | await Verify(notificationsTask); 191 | } 192 | 193 | [Fact] 194 | public async Task RemoveRange() 195 | { 196 | var list = new SortedListReactiveCollectionSource(); 197 | 198 | var notificationsTask = list.ReactiveCollection.Changes 199 | .Take(6) 200 | .ToArray() 201 | .ToTask(); 202 | 203 | list.AddRange(new[] { 1, 2, 3, 4 }); 204 | list.RemoveRange(new[] { 2, 4 }); 205 | 206 | await Verify(notificationsTask); 207 | } 208 | 209 | [Fact] 210 | public async Task RemoveRange2() 211 | { 212 | var list = new SortedListReactiveCollectionSource(); 213 | 214 | var notificationTask = list.ReactiveCollection.Changes 215 | .Take(6) 216 | .ToArray() 217 | .ToTask(); 218 | 219 | list.AddRange(new[] { 1, 2, 3, 4 }); 220 | list.RemoveRange(2, 2); 221 | 222 | await Verify(notificationTask); 223 | } 224 | 225 | [Fact] 226 | public async Task Replace() 227 | { 228 | var list = new SortedListReactiveCollectionSource(); 229 | 230 | var notificationsTask = list.ReactiveCollection.Changes 231 | .Take(8) 232 | .ToArray() 233 | .ToTask(); 234 | 235 | list.AddRange(new[] { 1, 2, 3, 4, 1 }); 236 | list.Replace(1, 5); 237 | 238 | await Verify(notificationsTask); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedSetReactiveCollectionSourceTest.Add.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1 9 | ], 10 | NewItems: [ 11 | 1 12 | ] 13 | } 14 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedSetReactiveCollectionSourceTest.Add_multiple.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | null, 3 | 0, 4 | 1, 5 | 1, 6 | 0, 7 | 4, 8 | 4 9 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedSetReactiveCollectionSourceTest.Clear.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Action: Reset 3 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedSetReactiveCollectionSourceTest.Clear_preserves_comparer.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | { 9 | Value: 1 10 | } 11 | ], 12 | NewItems: [ 13 | { 14 | Value: 1 15 | } 16 | ] 17 | }, 18 | { 19 | Action: Reset 20 | }, 21 | { 22 | Index: 0, 23 | Current: [ 24 | { 25 | Value: 2 26 | } 27 | ], 28 | NewItems: [ 29 | { 30 | Value: 2 31 | } 32 | ] 33 | }, 34 | { 35 | Index: 0, 36 | Current: [ 37 | { 38 | Value: 1 39 | }, 40 | { 41 | Value: 2 42 | } 43 | ], 44 | NewItems: [ 45 | { 46 | Value: 1 47 | } 48 | ] 49 | } 50 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedSetReactiveCollectionSourceTest.First_notification_is_reset.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Action: Reset 3 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedSetReactiveCollectionSourceTest.Remove.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Action: Reset 4 | }, 5 | { 6 | Index: 0, 7 | Current: [ 8 | 1 9 | ], 10 | NewItems: [ 11 | 1 12 | ] 13 | }, 14 | { 15 | Index: 0, 16 | OldItems: [ 17 | 1 18 | ], 19 | Action: Remove 20 | } 21 | ] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.Tests/SortedSetReactiveCollectionSourceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Threading.Tasks; 4 | using System.Threading.Tasks; 5 | using VerifyXunit; 6 | using Xunit; 7 | 8 | namespace ExRam.ReactiveCollections.Tests 9 | { 10 | public class SortedSetReactiveCollectionSourceTest : VerifyBase 11 | { 12 | public SortedSetReactiveCollectionSourceTest() : base() 13 | { 14 | 15 | } 16 | 17 | private struct StructNotImplementingIComparable 18 | { 19 | public StructNotImplementingIComparable(int value) 20 | { 21 | Value = value; 22 | } 23 | 24 | public int Value { get; } 25 | } 26 | 27 | [Fact] 28 | public async Task First_notification_is_reset() 29 | { 30 | var list = new SortedSetReactiveCollectionSource(); 31 | 32 | var notificationsTask = await list.ReactiveCollection.Changes 33 | .FirstAsync() 34 | .ToTask(); 35 | 36 | await Verify(notificationsTask); 37 | } 38 | 39 | [Fact] 40 | public async Task Add() 41 | { 42 | var list = new SortedSetReactiveCollectionSource(); 43 | 44 | var notificationsTask = list.ReactiveCollection.Changes 45 | .Take(2) 46 | .ToArray() 47 | .ToTask(); 48 | 49 | list.Add(1); 50 | 51 | await Verify(notificationsTask); 52 | } 53 | 54 | [Fact] 55 | public async Task Add_multiple() 56 | { 57 | var list = new SortedSetReactiveCollectionSource(); 58 | 59 | var notificationsTask = list.ReactiveCollection.Changes 60 | .Take(7) 61 | .Select(x => x.Index) 62 | .ToArray() 63 | .ToTask(); 64 | 65 | list.Add(1); // 0 66 | list.Add(3); // 1 67 | list.Add(2); // 1 68 | list.Add(0); // 0 69 | list.Add(6); // 4 70 | list.Add(5); // 4 71 | 72 | await Verify(notificationsTask); 73 | } 74 | 75 | [Fact] 76 | public async Task Clear() 77 | { 78 | var list = new SortedSetReactiveCollectionSource(); 79 | 80 | var notificationsTask = list.ReactiveCollection.Changes 81 | .Take(3) 82 | .FirstAsync() 83 | .ToTask(); 84 | 85 | list.Add(1); 86 | list.Clear(); 87 | 88 | await Verify(notificationsTask); 89 | } 90 | 91 | [Fact] 92 | public async Task Clear_preserves_comparer() 93 | { 94 | var list = new SortedSetReactiveCollectionSource(new Comparison((x, y) => x.Value.CompareTo(y.Value)).ToComparer()); 95 | 96 | var notificationsTask = list.ReactiveCollection.Changes 97 | .Take(5) 98 | .ToArray() 99 | .ToTask(); 100 | 101 | list.Add(new StructNotImplementingIComparable(1)); 102 | list.Clear(); 103 | list.Add(new StructNotImplementingIComparable(2)); 104 | list.Add(new StructNotImplementingIComparable(1)); 105 | 106 | await Verify(notificationsTask); 107 | } 108 | 109 | [Fact] 110 | public async Task Remove() 111 | { 112 | var list = new SortedSetReactiveCollectionSource(); 113 | 114 | var notificationsTask = list.ReactiveCollection.Changes 115 | .Take(3) 116 | .ToArray() 117 | .ToTask(); 118 | 119 | list.Add(1); 120 | list.Remove(1); 121 | 122 | await Verify(notificationsTask); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExRam.ReactiveCollections", "ExRam.ReactiveCollections\ExRam.ReactiveCollections.csproj", "{3F19ADDB-95AD-4909-8F10-5091BB439B3F}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExRam.ReactiveCollections.Tests", "ExRam.ReactiveCollections.Tests\ExRam.ReactiveCollections.Tests.csproj", "{120FA4A5-E564-4C09-B838-2BC1D82D7376}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {3F19ADDB-95AD-4909-8F10-5091BB439B3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {3F19ADDB-95AD-4909-8F10-5091BB439B3F}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {3F19ADDB-95AD-4909-8F10-5091BB439B3F}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {3F19ADDB-95AD-4909-8F10-5091BB439B3F}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {120FA4A5-E564-4C09-B838-2BC1D82D7376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {120FA4A5-E564-4C09-B838-2BC1D82D7376}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {120FA4A5-E564-4C09-B838-2BC1D82D7376}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {120FA4A5-E564-4C09-B838-2BC1D82D7376}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {97E88564-C146-482E-A190-EB365086B284} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/ExRam.ReactiveCollections.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1;net5.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ComparisonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace System 4 | { 5 | public static class ComparisonExtensions 6 | { 7 | #region ComparerImpl 8 | private sealed class ComparerImpl : IComparer 9 | { 10 | private readonly Comparison _comparison; 11 | 12 | public ComparerImpl(Comparison comparison) 13 | { 14 | _comparison = comparison; 15 | } 16 | 17 | public int Compare(T? x, T? y) 18 | { 19 | if (x is null || y is null) 20 | return x is null && y is null ? 0 : x is null ? -1 : 1; 21 | 22 | return _comparison(x, y); 23 | } 24 | } 25 | #endregion 26 | 27 | public static IComparer ToComparer(this Comparison comparison) 28 | { 29 | return new ComparerImpl(comparison); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ObservableExtensions (Multicast).cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Disposables; 2 | using System.Reactive.Subjects; 3 | 4 | namespace System.Reactive.Linq 5 | { 6 | public static partial class ObservableExtensions 7 | { 8 | #region MulticastConnectableObservable 9 | internal sealed class MulticastConnectableObservable : IConnectableObservable 10 | { 11 | private readonly IObservable _source; 12 | private readonly object _syncRoot = new(); 13 | private readonly Func> _subjectFactory; 14 | 15 | private ISubject? _currentSubject; 16 | private IDisposable? _currentSubscription; 17 | 18 | public MulticastConnectableObservable(IObservable source, Func> subjectFactory) 19 | { 20 | _source = source; 21 | _subjectFactory = subjectFactory; 22 | } 23 | 24 | public IDisposable Connect() 25 | { 26 | lock (_syncRoot) 27 | { 28 | if (_currentSubscription != null) 29 | throw new InvalidOperationException(); 30 | 31 | _currentSubject ??= _subjectFactory(); 32 | 33 | var sourceSubscription = _currentSubscription = _source.Subscribe(_currentSubject); 34 | 35 | return new CompositeDisposable( 36 | sourceSubscription, 37 | Disposable.Create(() => 38 | { 39 | lock (_syncRoot) 40 | { 41 | if (ReferenceEquals(_currentSubscription, sourceSubscription)) 42 | { 43 | _currentSubscription = null; 44 | 45 | (_currentSubject as IDisposable)?.Dispose(); 46 | _currentSubject = null; 47 | } 48 | } 49 | })); 50 | } 51 | } 52 | 53 | public IDisposable Subscribe(IObserver observer) 54 | { 55 | lock (_syncRoot) 56 | { 57 | _currentSubject ??= _subjectFactory(); 58 | 59 | return _currentSubject.Subscribe(observer); 60 | } 61 | } 62 | } 63 | #endregion 64 | 65 | internal static IConnectableObservable Multicast(this IObservable source, Func> subjectFactory) 66 | { 67 | return new MulticastConnectableObservable(source, subjectFactory); 68 | } 69 | 70 | internal static IConnectableObservable ReplayFresh(this IObservable observable, int bufferSize) 71 | { 72 | return observable 73 | .Multicast(() => new ReplaySubject(bufferSize)); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ObservableExtensions (Normalize).cs: -------------------------------------------------------------------------------- 1 | using ExRam.ReactiveCollections; 2 | 3 | namespace System.Reactive.Linq 4 | { 5 | public static partial class ObservableExtensions 6 | { 7 | internal static IObservable Normalize(this IObservable observable) 8 | where TNotification : ICollectionChangedNotification 9 | { 10 | return observable 11 | .Scan( 12 | (isFirst: true, notification: default(TNotification)), 13 | (state, notification) => (false, state.isFirst ? (TNotification)notification.ToResetNotification() : notification)) 14 | .Select(x => x.notification!); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ObservableExtensions (ToConnectableReactiveCollection).cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Subjects; 2 | using ExRam.ReactiveCollections; 3 | 4 | namespace System.Reactive.Linq 5 | { 6 | public static partial class ObservableExtensions 7 | { 8 | #region ToReactiveCollectionImpl 9 | private sealed class ToConnectableReactiveCollectionImpl : IConnectableReactiveCollection 10 | where TNotification : ICollectionChangedNotification 11 | { 12 | private readonly Func _connectFunction; 13 | private readonly IObservable _changes; 14 | 15 | public ToConnectableReactiveCollectionImpl(IObservable changes, Func connectFunction) 16 | { 17 | _changes = changes 18 | .Normalize(); 19 | 20 | _connectFunction = connectFunction; 21 | } 22 | 23 | IObservable IReactiveCollection.Changes => _changes; 24 | 25 | public IDisposable Connect() 26 | { 27 | return _connectFunction(); 28 | } 29 | } 30 | #endregion 31 | 32 | public static IConnectableReactiveCollection ToConnectableReactiveCollection(this IObservable changesObservable, Func connectFunction) 33 | where TNotification : ICollectionChangedNotification 34 | { 35 | return new ToConnectableReactiveCollectionImpl(changesObservable, connectFunction); 36 | } 37 | 38 | public static IConnectableReactiveCollection ToConnectableReactiveCollection(this IConnectableObservable changesObservable) 39 | where TNotification : ICollectionChangedNotification 40 | { 41 | return new ToConnectableReactiveCollectionImpl(changesObservable, changesObservable.Connect); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ObservableExtensions (ToNotifyCollectionChangedEventPattern).cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Specialized; 2 | using System.ComponentModel; 3 | 4 | namespace System.Reactive.Linq 5 | { 6 | public static partial class ObservableExtensions 7 | { 8 | #region NotifyCollectionChangedEventPatternSource 9 | private sealed class NotifyCollectionChangedEventPatternSource : EventPatternSourceBase, INotifyCollectionChanged 10 | { 11 | public NotifyCollectionChangedEventPatternSource(IObservable> source) 12 | : base(source, (invokeAction, eventPattern) => invokeAction(eventPattern.Sender, eventPattern.EventArgs)) 13 | { 14 | } 15 | 16 | public event NotifyCollectionChangedEventHandler? CollectionChanged 17 | { 18 | add 19 | { 20 | if (value is null) 21 | throw new ArgumentNullException(); 22 | 23 | Add(value, (o, e) => value?.Invoke(o, e)); 24 | } 25 | remove 26 | { 27 | if (value is null) 28 | throw new ArgumentNullException(); 29 | 30 | Remove(value); 31 | } 32 | } 33 | } 34 | #endregion 35 | 36 | #region NotifyPropertyChangedEventPatternSource 37 | private sealed class NotifyPropertyChangedEventPatternSource : EventPatternSourceBase, INotifyPropertyChanged 38 | { 39 | public NotifyPropertyChangedEventPatternSource(IObservable> source) 40 | : base(source, (invokeAction, eventPattern) => invokeAction(eventPattern.Sender, eventPattern.EventArgs)) 41 | { 42 | } 43 | 44 | public event PropertyChangedEventHandler? PropertyChanged 45 | { 46 | add 47 | { 48 | if (value is null) 49 | throw new ArgumentNullException(); 50 | 51 | Add(value, (o, e) => value?.Invoke(o, e)); 52 | } 53 | 54 | remove 55 | { 56 | if (value is null) 57 | throw new ArgumentNullException(); 58 | 59 | Remove(value); 60 | } 61 | } 62 | } 63 | #endregion 64 | 65 | public static INotifyCollectionChanged ToNotifyCollectionChangedEventPattern(this IObservable source, object sender) 66 | { 67 | return new NotifyCollectionChangedEventPatternSource(source.Select(x => new EventPattern(sender, x))); 68 | } 69 | 70 | public static INotifyPropertyChanged ToNotifyPropertyChangedEventPattern(this IObservable source, object sender) 71 | { 72 | return new NotifyPropertyChangedEventPatternSource(source.Select(x => new EventPattern(sender, x))); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ObservableExtensions (ToReactiveCollection).cs: -------------------------------------------------------------------------------- 1 | using ExRam.ReactiveCollections; 2 | 3 | namespace System.Reactive.Linq 4 | { 5 | public static partial class ObservableExtensions 6 | { 7 | #region ToReactiveCollectionImpl 8 | private sealed class ToReactiveCollectionImpl : IReactiveCollection 9 | where TNotification : ICollectionChangedNotification 10 | { 11 | private readonly IObservable _changes; 12 | 13 | public ToReactiveCollectionImpl(IObservable changes) 14 | { 15 | _changes = changes 16 | .Normalize(); 17 | } 18 | 19 | IObservable IReactiveCollection.Changes => _changes; 20 | } 21 | #endregion 22 | 23 | public static IReactiveCollection ToReactiveCollection(this IObservable changesObservable) 24 | where TNotification : ICollectionChangedNotification 25 | { 26 | return new ToReactiveCollectionImpl(changesObservable); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (AsReactiveCollection).cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | 4 | namespace ExRam.ReactiveCollections 5 | { 6 | public static partial class ReactiveCollectionExtensions 7 | { 8 | #region AsReactiveCollectionImpl 9 | private sealed class AsReactiveCollectionImpl : IReactiveCollection 10 | where TNotification : ICollectionChangedNotification 11 | { 12 | private readonly IReactiveCollection _reactiveCollection; 13 | 14 | public AsReactiveCollectionImpl(IReactiveCollection reactiveCollection) 15 | { 16 | _reactiveCollection = reactiveCollection; 17 | } 18 | 19 | IObservable IReactiveCollection.Changes => _reactiveCollection.Changes.AsObservable(); 20 | } 21 | #endregion 22 | 23 | public static IReactiveCollection AsReactiveCollection(this IReactiveCollection reactiveCollection) 24 | where TNotification : ICollectionChangedNotification 25 | { 26 | return new AsReactiveCollectionImpl(reactiveCollection); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (Concat).cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Collections.Specialized; 5 | using System.Linq; 6 | using System.Reactive.Linq; 7 | 8 | namespace ExRam.ReactiveCollections 9 | { 10 | public static partial class ReactiveCollectionExtensions 11 | { 12 | #region ConcatListReactiveCollection 13 | private sealed class ConcatListReactiveCollection : IReactiveCollection> 14 | { 15 | #region Node 16 | private abstract class Node 17 | { 18 | protected Node(ImmutableList? list, int maxIndex) 19 | { 20 | List = list; 21 | MaxIndex = maxIndex; 22 | } 23 | 24 | public abstract Node ReplaceNode(ImmutableList newList, int index, out int? replacementOffset, out ImmutableList? oldList); 25 | 26 | public int MaxIndex { get; } 27 | public ImmutableList? List { get; } 28 | } 29 | 30 | private sealed class InnerNode : Node 31 | { 32 | private readonly Node _left; 33 | private readonly Node _right; 34 | 35 | public InnerNode(Node left, Node right) : base( 36 | left.List != null && right.List != null 37 | ? left.List.AddRange(right.List) 38 | : null, 39 | Math.Max(left.MaxIndex, right.MaxIndex)) 40 | { 41 | _left = left; 42 | _right = right; 43 | } 44 | 45 | public override Node ReplaceNode(ImmutableList newList, int index, out int? replacementOffset, out ImmutableList? oldList) 46 | { 47 | if (MaxIndex < index) 48 | throw new InvalidOperationException(); 49 | 50 | if (_left.MaxIndex >= index) 51 | return new InnerNode(_left.ReplaceNode(newList, index, out replacementOffset, out oldList), _right); 52 | 53 | var newNode = new InnerNode(_left, _right.ReplaceNode(newList, index, out replacementOffset, out oldList)); 54 | replacementOffset += _left.List?.Count; 55 | 56 | return newNode; 57 | } 58 | } 59 | 60 | private sealed class TerminalNode : Node 61 | { 62 | public TerminalNode(ImmutableList? list, int maxIndex) : base(list, maxIndex) 63 | { 64 | } 65 | 66 | public override Node ReplaceNode(ImmutableList newList, int index, out int? replacementOffset, out ImmutableList? oldList) 67 | { 68 | oldList = List; 69 | replacementOffset = 0; 70 | 71 | return new TerminalNode(newList, index); 72 | } 73 | } 74 | #endregion 75 | 76 | #region IndexedNotification 77 | private readonly struct IndexedNotification 78 | { 79 | public int Index { get; } 80 | public ListChangedNotification Notification { get; } 81 | 82 | public IndexedNotification(int index, ListChangedNotification notification) 83 | { 84 | Index = index; 85 | Notification = notification; 86 | } 87 | } 88 | #endregion 89 | 90 | public ConcatListReactiveCollection( 91 | IObservable>[] sources, 92 | IEqualityComparer equalityComparer) 93 | { 94 | Changes = Observable 95 | .Defer(() => 96 | { 97 | var syncRoot = new object(); 98 | var rootNode = GetTree(0, sources.Length - 1); 99 | 100 | return sources 101 | .Select((observable, i) => observable 102 | .Select(notification => new IndexedNotification(i, notification))) 103 | .Merge() 104 | .Select(tuple => 105 | { 106 | lock(syncRoot) 107 | { 108 | rootNode = rootNode.ReplaceNode(tuple.Notification.Current, tuple.Index, out var offset, out var oldList); 109 | 110 | if (rootNode.List != null) 111 | { 112 | switch (tuple.Notification.Action) 113 | { 114 | case NotifyCollectionChangedAction.Add: 115 | return new ListChangedNotification(rootNode.List, tuple.Notification.Action, ImmutableList.Empty, tuple.Notification.NewItems, tuple.Notification.Index + offset); 116 | case NotifyCollectionChangedAction.Move: 117 | case NotifyCollectionChangedAction.Replace: 118 | return new ListChangedNotification(rootNode.List, tuple.Notification.Action, tuple.Notification.OldItems, tuple.Notification.NewItems, tuple.Notification.Index + offset); 119 | case NotifyCollectionChangedAction.Remove: 120 | return new ListChangedNotification(rootNode.List, tuple.Notification.Action, tuple.Notification.OldItems, ImmutableList.Empty, tuple.Notification.Index + offset); 121 | case NotifyCollectionChangedAction.Reset: 122 | { 123 | if (oldList == null) 124 | return new ListChangedNotification(rootNode.List, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null); 125 | 126 | if (oldList.Count > 0) 127 | { 128 | return tuple.Notification.Current.Count > 0 129 | ? new ListChangedNotification(rootNode.List, NotifyCollectionChangedAction.Replace, oldList, tuple.Notification.Current, offset) 130 | : new ListChangedNotification(rootNode.List, NotifyCollectionChangedAction.Remove, oldList, ImmutableList.Empty, offset); 131 | } 132 | 133 | return tuple.Notification.Current.Count > 0 134 | ? new ListChangedNotification(rootNode.List, NotifyCollectionChangedAction.Add, ImmutableList.Empty, tuple.Notification.Current, offset) 135 | : null; 136 | } 137 | default: 138 | throw new InvalidOperationException(); 139 | } 140 | } 141 | 142 | return null; 143 | } 144 | }) 145 | .Where(x => x != null) 146 | .Select(x => x!); 147 | }) 148 | .ReplayFresh(1) 149 | .RefCount() 150 | .Normalize(); 151 | } 152 | 153 | public IObservable> Changes { get; } 154 | 155 | private Node GetTree(int min, int max) 156 | { 157 | if (min == max) 158 | return new TerminalNode(null, min); 159 | 160 | var half = (max - min + 1) / 2; 161 | 162 | return new InnerNode(GetTree(min, min + half - 1), GetTree(min + half, max)); 163 | } 164 | } 165 | #endregion 166 | 167 | public static IReactiveCollection> Concat(this IEnumerable>> collections) 168 | { 169 | return collections.Concat(EqualityComparer.Default); 170 | } 171 | 172 | public static IReactiveCollection> Concat(this IEnumerable>> collections, IEqualityComparer equalityComparer) 173 | { 174 | var sourcesArray = collections 175 | .Select(x => x.Changes) 176 | .ToArray(); 177 | 178 | if (sourcesArray.Length == 0) 179 | { 180 | return Observable 181 | .Return(new ListChangedNotification(ImmutableList.Empty, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null)) 182 | .ToReactiveCollection(); 183 | } 184 | 185 | return sourcesArray.Length == 1 186 | ? sourcesArray[0].ToReactiveCollection() 187 | : new ConcatListReactiveCollection(sourcesArray, equalityComparer); 188 | } 189 | 190 | public static IReactiveCollection> Concat(this IReactiveCollection> source1, IReactiveCollection> source2) 191 | { 192 | return source1.Concat(source2, EqualityComparer.Default); 193 | } 194 | 195 | public static IReactiveCollection> Concat(this IReactiveCollection> source1, IReactiveCollection> source2, IEqualityComparer equalityComparer) 196 | { 197 | return new ConcatListReactiveCollection(new[] { source1.Changes, source2.Changes } , equalityComparer); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (GetValueObservable).cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | 4 | namespace ExRam.ReactiveCollections 5 | { 6 | public static partial class ReactiveCollectionExtensions 7 | { 8 | public static IObservable GetValues(this IReactiveCollection> reactiveCollection, TKey key) 9 | where TKey : notnull 10 | { 11 | return reactiveCollection.Changes 12 | .Where(x => x.Current.ContainsKey(key)) 13 | .Select(x => x.Current[key]); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (ObserveOn).cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Concurrency; 2 | using System.Reactive.Linq; 3 | using System.Threading; 4 | 5 | namespace ExRam.ReactiveCollections 6 | { 7 | public static partial class ReactiveCollectionExtensions 8 | { 9 | public static IReactiveCollection ObserveOn(this IReactiveCollection source, SynchronizationContext syncContext) where TNotification : ICollectionChangedNotification 10 | { 11 | return source.Changes 12 | .ObserveOn(syncContext) 13 | .ToReactiveCollection(); 14 | } 15 | 16 | public static IReactiveCollection ObserveOn(this IReactiveCollection source, IScheduler scheduler) where TNotification : ICollectionChangedNotification 17 | { 18 | return source.Changes 19 | .ObserveOn(scheduler) 20 | .ToReactiveCollection(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (Select).cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExRam.ReactiveCollections 5 | { 6 | public static partial class ReactiveCollectionExtensions 7 | { 8 | public static IReactiveCollection> Select(this IReactiveCollection> source, Func selector) 9 | { 10 | return source.Select(selector, EqualityComparer.Default); 11 | } 12 | 13 | public static IReactiveCollection> Select(this IReactiveCollection> source, Func selector, IEqualityComparer equalityComparer) 14 | { 15 | return source.WhereSelect(null, selector, equalityComparer); 16 | } 17 | 18 | public static IReactiveCollection> Select(this IReactiveCollection> source, Func selector) 19 | where TKey : notnull 20 | { 21 | return source.WhereSelect(null, selector); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (Sort).cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ExRam.ReactiveCollections 4 | { 5 | public static partial class ReactiveCollectionExtensions 6 | { 7 | public static IReactiveCollection> Sort(this IReactiveCollection> source) 8 | { 9 | return source.Sort(Comparer.Default, EqualityComparer.Default); 10 | } 11 | 12 | public static IReactiveCollection> Sort(this IReactiveCollection> source, IComparer comparer) 13 | { 14 | return source.Sort(comparer, EqualityComparer.Default); 15 | } 16 | 17 | public static IReactiveCollection> Sort(this IReactiveCollection> source, IEqualityComparer equalityComparer) 18 | { 19 | return source.Sort(Comparer.Default, equalityComparer); 20 | } 21 | 22 | public static IReactiveCollection> Sort(this IReactiveCollection> source, IComparer comparer, IEqualityComparer equalityComparer) 23 | { 24 | return source 25 | .Transform( 26 | SortedListChangedNotification.Reset.WithComparer(comparer), 27 | (source, target) => target.Sort(source, equalityComparer)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (SortSet).cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ExRam.ReactiveCollections 4 | { 5 | public static partial class ReactiveCollectionExtensions 6 | { 7 | public static IReactiveCollection> SortSet(this IReactiveCollection> source) 8 | { 9 | return source.SortSet(Comparer.Default); 10 | } 11 | 12 | public static IReactiveCollection> SortSet(this IReactiveCollection> source, IComparer comparer) 13 | { 14 | return source 15 | .Transform( 16 | SortedSetChangedNotification.Reset.WithComparer(comparer), 17 | (source, target) => target.Sort(source)); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (SubscribeOn).cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Concurrency; 2 | using System.Reactive.Linq; 3 | using System.Threading; 4 | 5 | namespace ExRam.ReactiveCollections 6 | { 7 | public static partial class ReactiveCollectionExtensions 8 | { 9 | public static IReactiveCollection SubscribeOn(this IReactiveCollection source, SynchronizationContext syncContext) where TNotification : ICollectionChangedNotification 10 | { 11 | return source.Changes 12 | .SubscribeOn(syncContext) 13 | .ToReactiveCollection(); 14 | } 15 | 16 | public static IReactiveCollection SubscribeOn(this IReactiveCollection source, IScheduler scheduler) where TNotification : ICollectionChangedNotification 17 | { 18 | return source.Changes 19 | .SubscribeOn(scheduler) 20 | .ToReactiveCollection(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (ToObservableCollection).cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.Collections.Specialized; 6 | using System.ComponentModel; 7 | using System.Reactive.Disposables; 8 | using System.Reactive.Linq; 9 | 10 | namespace ExRam.ReactiveCollections 11 | { 12 | public static partial class ReactiveCollectionExtensions 13 | { 14 | #region ReactiveReadOnlyObservableCollection 15 | private sealed class ReactiveReadOnlyObservableCollection : IList, IReadOnlyList, IList, INotifyCollectionChanged, INotifyPropertyChanged 16 | { 17 | #region Events 18 | public event PropertyChangedEventHandler? PropertyChanged 19 | { 20 | add => _propertyChanged.PropertyChanged += value; 21 | 22 | remove => _propertyChanged.PropertyChanged -= value; 23 | } 24 | 25 | public event NotifyCollectionChangedEventHandler? CollectionChanged 26 | { 27 | add => _collectionChanged.CollectionChanged += value; 28 | 29 | remove => _collectionChanged.CollectionChanged -= value; 30 | } 31 | #endregion 32 | 33 | private readonly INotifyPropertyChanged _propertyChanged; 34 | private readonly INotifyCollectionChanged _collectionChanged; 35 | 36 | private IReadOnlyCollection _currentList = ImmutableList.Empty; 37 | 38 | public ReactiveReadOnlyObservableCollection(IObservable> source) 39 | { 40 | var eventArgs = source 41 | .Do(notification => _currentList = notification.Current) 42 | .Skip(1) 43 | .SelectMany(notification => Observable 44 | .Create(obs => 45 | { 46 | switch (notification.Action) 47 | { 48 | case NotifyCollectionChangedAction.Add: 49 | { 50 | obs.OnNext(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)notification.NewItems, notification.Index!.Value)); 51 | obs.OnNext(new PropertyChangedEventArgs("Count")); 52 | obs.OnNext(new PropertyChangedEventArgs("Item[]")); 53 | 54 | break; 55 | } 56 | 57 | case NotifyCollectionChangedAction.Remove: 58 | { 59 | 60 | obs.OnNext(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)notification.OldItems, notification.Index!.Value)); 61 | obs.OnNext(new PropertyChangedEventArgs("Count")); 62 | obs.OnNext(new PropertyChangedEventArgs("Item[]")); 63 | 64 | break; 65 | } 66 | 67 | case NotifyCollectionChangedAction.Replace: 68 | { 69 | obs.OnNext(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, (IList)notification.NewItems, (IList)notification.OldItems, notification.Index!.Value)); 70 | obs.OnNext(new PropertyChangedEventArgs("Item[]")); 71 | 72 | break; 73 | } 74 | 75 | default: 76 | { 77 | if (_currentList.Count > 0) 78 | { 79 | _currentList = ImmutableList.Empty; 80 | 81 | obs.OnNext(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 82 | obs.OnNext(new PropertyChangedEventArgs("Count")); 83 | obs.OnNext(new PropertyChangedEventArgs("Item[]")); 84 | } 85 | 86 | if (notification.Current.Count > 0) 87 | { 88 | _currentList = notification.Current; 89 | 90 | obs.OnNext(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)notification.Current, 0)); 91 | obs.OnNext(new PropertyChangedEventArgs("Count")); 92 | obs.OnNext(new PropertyChangedEventArgs("Item[]")); 93 | } 94 | 95 | break; 96 | } 97 | } 98 | 99 | return Disposable.Empty; 100 | })) 101 | .Publish() 102 | .RefCount(); 103 | 104 | _collectionChanged = eventArgs 105 | .OfType() 106 | .ToNotifyCollectionChangedEventPattern(this); 107 | 108 | _propertyChanged = eventArgs 109 | .OfType() 110 | .ToNotifyPropertyChangedEventPattern(this); 111 | } 112 | 113 | void ICollection.Add(T item) => throw new NotSupportedException(); 114 | 115 | public void Clear() => throw new NotSupportedException(); 116 | 117 | bool ICollection.Contains(T item) 118 | { 119 | return !(_currentList is ICollection list) 120 | ? throw new InvalidOperationException() 121 | : list.Contains(item); 122 | } 123 | 124 | void ICollection.CopyTo(T[] array, int arrayIndex) 125 | { 126 | if (!(_currentList is ICollection list)) 127 | throw new InvalidOperationException(); 128 | 129 | list.CopyTo(array, arrayIndex); 130 | } 131 | 132 | public int Count => _currentList.Count; 133 | 134 | public bool IsReadOnly => true; 135 | 136 | bool ICollection.Remove(T item) => throw new NotSupportedException(); 137 | 138 | IEnumerator IEnumerable.GetEnumerator() => _currentList.GetEnumerator(); 139 | 140 | IEnumerator IEnumerable.GetEnumerator() => _currentList.GetEnumerator(); 141 | 142 | int IList.IndexOf(T item) 143 | { 144 | return _currentList is IList list 145 | ? list.IndexOf(item) 146 | : throw new InvalidOperationException(); 147 | } 148 | 149 | void IList.Insert(int index, T item) => throw new NotSupportedException(); 150 | 151 | void IList.RemoveAt(int index) => throw new NotSupportedException(); 152 | 153 | T IList.this[int index] 154 | { 155 | get => _currentList is IReadOnlyList list 156 | ? list[index] 157 | : throw new InvalidOperationException(); 158 | set => throw new NotSupportedException(); 159 | } 160 | 161 | T IReadOnlyList.this[int index] => ((IList)this)[index]; 162 | 163 | int IList.Add(object? value) => throw new NotSupportedException(); 164 | 165 | bool IList.Contains(object? value) => ((IList)_currentList).Contains(value); 166 | 167 | int IList.IndexOf(object? value) => ((IList)_currentList).IndexOf(value); 168 | 169 | void IList.Insert(int index, object? value) => throw new NotSupportedException(); 170 | 171 | bool IList.IsFixedSize => false; 172 | 173 | void IList.Remove(object? value) => throw new NotSupportedException(); 174 | 175 | void IList.RemoveAt(int index) => throw new NotSupportedException(); 176 | 177 | object? IList.this[int index] 178 | { 179 | get => ((IList)this)[index]; 180 | set => throw new NotSupportedException(); 181 | } 182 | 183 | void ICollection.CopyTo(Array array, int index) => ((ICollection)_currentList).CopyTo(array, index); 184 | 185 | bool ICollection.IsSynchronized => false; 186 | 187 | object ICollection.SyncRoot => this; 188 | } 189 | #endregion 190 | 191 | public static ICollection ToObservableCollection(this IReactiveCollection> source) 192 | { 193 | return new ReactiveReadOnlyObservableCollection(source.Changes); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (Tranform).cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Linq; 5 | 6 | namespace ExRam.ReactiveCollections 7 | { 8 | public static partial class ReactiveCollectionExtensions 9 | { 10 | private static IReactiveCollection Transform(this IReactiveCollection source, TTargetNotification resetTargetNotification, Func> transformation) 11 | where TSourceNotification : ICollectionChangedNotification 12 | where TTargetNotification : ICollectionChangedNotification 13 | { 14 | return source 15 | .Changes 16 | .Scan( 17 | new[] { resetTargetNotification }, 18 | (currentTargetNotification, sourceNotification) => 19 | { 20 | var newRet = transformation(sourceNotification, currentTargetNotification[^1]) 21 | .ToArray(); 22 | 23 | return newRet.Length > 0 ? newRet : 24 | currentTargetNotification.Length > 0 25 | ? new[] { currentTargetNotification[0] } 26 | : currentTargetNotification; 27 | }) 28 | .SelectMany(x => x) 29 | .DistinctUntilChanged() 30 | .ToReactiveCollection(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (Where).cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExRam.ReactiveCollections 5 | { 6 | public static partial class ReactiveCollectionExtensions 7 | { 8 | public static IReactiveCollection> Where(this IReactiveCollection> source, Predicate filter) 9 | { 10 | return source.WhereSelect(filter, _ => _, EqualityComparer.Default); 11 | } 12 | 13 | public static IReactiveCollection> Where(this IReactiveCollection> source, Predicate filter) 14 | where TKey : notnull 15 | { 16 | return source.WhereSelect(filter, _ => _); 17 | } 18 | 19 | internal static IReactiveCollection> WhereSelect(this IReactiveCollection> source, Predicate? filter, Func selector, IEqualityComparer? equalityComparer = null) 20 | { 21 | return source 22 | .Transform( 23 | ListChangedNotification.Reset, 24 | (source, target) => target.WhereSelect(source, filter, selector)); 25 | } 26 | 27 | internal static IReactiveCollection> WhereSelect(this IReactiveCollection> source, Predicate? filter, Func selector) 28 | where TKey : notnull 29 | { 30 | return source 31 | .Transform( 32 | DictionaryChangedNotification.Reset, 33 | (source, target) => target.WhereSelect(source, filter, selector)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Extensions/ReactiveCollectionExtensions (WithChanges).cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | 4 | namespace ExRam.ReactiveCollections 5 | { 6 | public static partial class ReactiveCollectionExtensions 7 | { 8 | public static IReactiveCollection WithChanges(this IReactiveCollection source, Func, IObservable> changesTransformation) where TNotification : ICollectionChangedNotification 9 | { 10 | return changesTransformation(source.Changes) 11 | .ToReactiveCollection(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Interfaces/ICollectionChangedNotification.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | 5 | namespace ExRam.ReactiveCollections 6 | { 7 | public interface ICollectionChangedNotification 8 | { 9 | ICollectionChangedNotification ToResetNotification(); 10 | 11 | IEnumerable OldItems 12 | { 13 | get; 14 | } 15 | 16 | IEnumerable NewItems 17 | { 18 | get; 19 | } 20 | 21 | NotifyCollectionChangedAction Action 22 | { 23 | get; 24 | } 25 | 26 | IEnumerable Current 27 | { 28 | get; 29 | } 30 | } 31 | 32 | public interface ICollectionChangedNotification : ICollectionChangedNotification 33 | { 34 | new ICollectionChangedNotification ToResetNotification(); 35 | 36 | new IReadOnlyList OldItems 37 | { 38 | get; 39 | } 40 | 41 | new IReadOnlyList NewItems 42 | { 43 | get; 44 | } 45 | 46 | new IReadOnlyCollection Current 47 | { 48 | get; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Interfaces/IConnectableReactiveCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ExRam.ReactiveCollections 4 | { 5 | public interface IConnectableReactiveCollection : IReactiveCollection 6 | where TNotification : ICollectionChangedNotification 7 | { 8 | IDisposable Connect(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Interfaces/IIndexedCollectionChangedNotification.cs: -------------------------------------------------------------------------------- 1 | namespace ExRam.ReactiveCollections 2 | { 3 | public interface IIndexedCollectionChangedNotification : ICollectionChangedNotification 4 | { 5 | int? Index { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Interfaces/IReactiveCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ExRam.ReactiveCollections 4 | { 5 | public interface IReactiveCollection 6 | where TNotification : ICollectionChangedNotification 7 | { 8 | IObservable Changes 9 | { 10 | get; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Interfaces/IReactiveCollectionSource.cs: -------------------------------------------------------------------------------- 1 | namespace ExRam.ReactiveCollections 2 | { 3 | public interface IReactiveCollectionSource 4 | where TNotification : ICollectionChangedNotification 5 | { 6 | IReactiveCollection ReactiveCollection 7 | { 8 | get; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Notifications/CollectionChangedNotification.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | 5 | namespace ExRam.ReactiveCollections 6 | { 7 | public abstract class CollectionChangedNotification : ICollectionChangedNotification 8 | { 9 | protected CollectionChangedNotification(IReadOnlyCollection current, NotifyCollectionChangedAction action, IReadOnlyList oldItems, IReadOnlyList newItems) 10 | { 11 | Action = action; 12 | Current = current; 13 | OldItems = oldItems; 14 | NewItems = newItems; 15 | } 16 | 17 | public abstract ICollectionChangedNotification ToResetNotification(); 18 | 19 | public IReadOnlyList OldItems { get; } 20 | 21 | public IReadOnlyList NewItems { get; } 22 | 23 | public NotifyCollectionChangedAction Action { get; } 24 | 25 | public IReadOnlyCollection Current { get; } 26 | 27 | ICollectionChangedNotification ICollectionChangedNotification.ToResetNotification() => ToResetNotification(); 28 | 29 | IEnumerable ICollectionChangedNotification.Current => Current; 30 | 31 | IEnumerable ICollectionChangedNotification.NewItems => NewItems; 32 | 33 | IEnumerable ICollectionChangedNotification.OldItems => OldItems; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Notifications/DictionaryChangedNotification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Collections.Specialized; 5 | using System.Linq; 6 | 7 | namespace ExRam.ReactiveCollections 8 | { 9 | public sealed class DictionaryChangedNotification : CollectionChangedNotification> 10 | where TKey: notnull 11 | { 12 | public static readonly DictionaryChangedNotification Reset = new (ImmutableDictionary.Empty, NotifyCollectionChangedAction.Reset, ImmutableList>.Empty, ImmutableList>.Empty); 13 | 14 | public DictionaryChangedNotification( 15 | ImmutableDictionary current, 16 | NotifyCollectionChangedAction action, 17 | IReadOnlyList> oldItems, 18 | IReadOnlyList> newItems) : base(current, action, oldItems, newItems) 19 | { 20 | 21 | } 22 | 23 | public override ICollectionChangedNotification> ToResetNotification() => new DictionaryChangedNotification(Current, NotifyCollectionChangedAction.Reset, ImmutableList>.Empty, ImmutableList>.Empty); 24 | 25 | public new ImmutableDictionary Current => (ImmutableDictionary)base.Current; 26 | 27 | public DictionaryChangedNotification WithComparers(IEqualityComparer keyComparer) 28 | { 29 | return ReferenceEquals(Current.KeyComparer, keyComparer) 30 | ? this 31 | : new(Current.WithComparers(keyComparer), Action, OldItems, NewItems); 32 | } 33 | 34 | internal DictionaryChangedNotification SetItems(IEnumerable> items) 35 | { 36 | var oldList = Current; 37 | var newList = oldList.SetItems(items); 38 | 39 | return oldList != newList 40 | ? new (newList, NotifyCollectionChangedAction.Reset, ImmutableList>.Empty, ImmutableList>.Empty) 41 | : this; 42 | } 43 | 44 | internal DictionaryChangedNotification SetItem(TKey key, TValue value) 45 | { 46 | var oldList = Current; 47 | var newList = oldList.SetItem(key, value); 48 | 49 | return oldList != newList 50 | ? new(newList, NotifyCollectionChangedAction.Replace, ImmutableList.Create(new KeyValuePair(key, oldList[key])), ImmutableList.Create(new KeyValuePair(key, value))) 51 | : this; 52 | } 53 | 54 | internal DictionaryChangedNotification Remove(TKey key) 55 | { 56 | var newList = Current.Remove(key); 57 | 58 | return Current != newList 59 | ? new(newList, NotifyCollectionChangedAction.Remove, ImmutableList.Create(new KeyValuePair(key, Current[key])), ImmutableList>.Empty) 60 | : this; 61 | } 62 | 63 | internal DictionaryChangedNotification RemoveRange(IEnumerable keys) 64 | { 65 | var newList = Current.RemoveRange(keys); 66 | 67 | var removed = keys 68 | .Select(key => Current.TryGetValue(key, out var value) 69 | ? new KeyValuePair(key, value) 70 | : default(KeyValuePair?)) 71 | .Where(x => x.HasValue) 72 | .Select(x => x!.Value) 73 | .ToImmutableList(); 74 | 75 | return Current != newList 76 | ? new(newList, NotifyCollectionChangedAction.Remove, removed, ImmutableList>.Empty) 77 | : this; 78 | } 79 | 80 | internal DictionaryChangedNotification Clear() 81 | { 82 | return !Current.IsEmpty 83 | ? new(ImmutableDictionary.Empty, NotifyCollectionChangedAction.Reset, ImmutableList>.Empty, ImmutableList>.Empty) 84 | : this; 85 | } 86 | 87 | internal DictionaryChangedNotification AddRange(IEnumerable> pairs) 88 | { 89 | var immutablePairs = ImmutableList.CreateRange(pairs); 90 | 91 | if (immutablePairs.IsEmpty) 92 | return this; 93 | 94 | var current = Current; 95 | var newDict = current.AddRange(immutablePairs); 96 | 97 | return newDict != current 98 | ? new(newDict, NotifyCollectionChangedAction.Add, ImmutableList>.Empty, immutablePairs) 99 | : this; 100 | } 101 | 102 | internal DictionaryChangedNotification Add(KeyValuePair kvp) 103 | { 104 | return new(Current.Add(kvp.Key, kvp.Value), NotifyCollectionChangedAction.Add, ImmutableList>.Empty, ImmutableList.Create(kvp)); 105 | } 106 | 107 | internal IEnumerable> WhereSelect(ICollectionChangedNotification> notification, Predicate? filter, Func selector) 108 | { 109 | switch (notification.Action) 110 | { 111 | #region Add 112 | case NotifyCollectionChangedAction.Add: 113 | { 114 | var filteredItems = filter != null 115 | ? notification.NewItems.Where(x => filter(x.Value)) 116 | : notification.NewItems; 117 | 118 | var selectedItems = filteredItems 119 | .Select(x => new KeyValuePair(x.Key, selector(x.Value))); 120 | 121 | yield return AddRange(selectedItems); 122 | 123 | break; 124 | } 125 | #endregion 126 | 127 | #region Remove 128 | case NotifyCollectionChangedAction.Remove: 129 | { 130 | yield return RemoveRange(notification.OldItems.Select(x => x.Key)); 131 | 132 | break; 133 | } 134 | #endregion 135 | 136 | #region Replace 137 | case NotifyCollectionChangedAction.Replace: 138 | { 139 | var filteredItems = filter != null 140 | ? notification.NewItems.Where(x => filter(x.Value)) 141 | : notification.NewItems; 142 | 143 | var removed = RemoveRange(notification.OldItems 144 | .Select(x => x.Key)); ; 145 | 146 | yield return removed; 147 | yield return removed.AddRange(filteredItems 148 | .Select(x => new KeyValuePair(x.Key, selector(x.Value)))); 149 | 150 | break; 151 | } 152 | #endregion 153 | 154 | #region default 155 | default: 156 | { 157 | var cleared = Clear(); 158 | 159 | if (notification is DictionaryChangedNotification dictionaryChangedNotification) 160 | cleared = cleared.WithComparers(dictionaryChangedNotification.Current.KeyComparer); 161 | 162 | yield return cleared; 163 | 164 | if (notification.Current.Count > 0) 165 | { 166 | var filteredItems = filter != null 167 | ? notification.NewItems.Where(x => filter(x.Value)) 168 | : notification.NewItems; 169 | 170 | yield return cleared.AddRange(filteredItems 171 | .Select(x => new KeyValuePair(x.Key, selector(x.Value)))); 172 | } 173 | 174 | break; 175 | } 176 | #endregion 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Notifications/SortedListChangedNotification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Collections.Specialized; 5 | 6 | namespace ExRam.ReactiveCollections 7 | { 8 | public sealed class SortedListChangedNotification : CollectionChangedNotification, IIndexedCollectionChangedNotification 9 | { 10 | public static readonly SortedListChangedNotification Reset = new (ImmutableList.Empty, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null, Comparer.Default); 11 | 12 | public SortedListChangedNotification(IReadOnlyCollection current, NotifyCollectionChangedAction action, IReadOnlyList oldItems, IReadOnlyList newItems, int? index, IComparer comparer) : base(current, action, oldItems, newItems) 13 | { 14 | Index = index; 15 | Comparer = comparer; 16 | } 17 | 18 | public override ICollectionChangedNotification ToResetNotification() 19 | { 20 | return new SortedListChangedNotification(Current, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null, Comparer); 21 | } 22 | 23 | internal SortedListChangedNotification WithComparer(IComparer comparer) 24 | { 25 | return new(Current, Action, OldItems, NewItems, Index, comparer); 26 | } 27 | 28 | internal SortedListChangedNotification Add(T item) => Insert(FindInsertionIndex(item), item); 29 | 30 | internal SortedListChangedNotification Clear() 31 | { 32 | return !Current.IsEmpty 33 | ? new (ImmutableList.Empty, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null, Comparer) 34 | : this; 35 | } 36 | 37 | internal SortedListChangedNotification Insert(int index, T item) 38 | { 39 | return new(Current.Insert(index, item), NotifyCollectionChangedAction.Add, ImmutableList.Empty, ImmutableList.Create(item), index, Comparer); 40 | } 41 | 42 | internal SortedListChangedNotification Remove(T item, IEqualityComparer equalityComparer) 43 | { 44 | var index = Current.IndexOf(item, equalityComparer); 45 | 46 | if (index > -1) 47 | { 48 | var oldItem = Current[index]; 49 | var newList = Current.RemoveAt(index); 50 | 51 | return Current != newList 52 | ? new (newList, NotifyCollectionChangedAction.Remove, ImmutableList.Create(oldItem), ImmutableList.Empty, index, Comparer) 53 | : this; 54 | } 55 | 56 | return this; 57 | } 58 | 59 | internal SortedListChangedNotification RemoveAt(int index) 60 | { 61 | var oldItem = Current[index]; 62 | var newList = Current.RemoveAt(index); 63 | 64 | return Current != newList 65 | ? new(newList, NotifyCollectionChangedAction.Remove, ImmutableList.Create(oldItem), ImmutableList.Empty, index, Comparer) 66 | : this; 67 | } 68 | 69 | internal SortedListChangedNotification RemoveRange(int index, int count) 70 | { 71 | var range = Current.GetRange(index, count); 72 | var newList = Current.RemoveRange(index, count); 73 | 74 | return newList != Current 75 | ? new (newList, NotifyCollectionChangedAction.Remove, range, ImmutableList.Empty, index, Comparer) 76 | : this; 77 | } 78 | 79 | internal SortedListChangedNotification RemoveRange(IEnumerable items, IEqualityComparer equalityComparer) 80 | { 81 | var removedItems = ImmutableList.CreateRange(items); 82 | 83 | if (removedItems.Count > 0) 84 | { 85 | if (removedItems.Count > 1) 86 | { 87 | var newList = Current.RemoveRange(removedItems, equalityComparer); 88 | if (Current != newList) 89 | return new (newList, NotifyCollectionChangedAction.Remove, removedItems, ImmutableList.Empty, null, Comparer); 90 | } 91 | else 92 | return Remove(removedItems[0], equalityComparer); 93 | } 94 | 95 | return this; 96 | } 97 | 98 | internal SortedListChangedNotification RemoveAll(Predicate match) 99 | { 100 | var newList = Current.RemoveAll(match); 101 | return new (newList, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null, Comparer); 102 | } 103 | 104 | internal IEnumerable> Sort(ICollectionChangedNotification notification, IEqualityComparer equalityComparer) 105 | { 106 | switch (notification.Action) 107 | { 108 | #region Add 109 | case NotifyCollectionChangedAction.Add: 110 | { 111 | var current = this; 112 | 113 | foreach (var newItem in notification.NewItems) 114 | { 115 | current = current.Add(newItem); //TODO 116 | yield return current; 117 | } 118 | 119 | break; 120 | } 121 | #endregion 122 | 123 | #region Remove 124 | case NotifyCollectionChangedAction.Remove: 125 | { 126 | var current = this; 127 | 128 | foreach (var oldItem in notification.OldItems) 129 | { 130 | current = current.Remove(oldItem, equalityComparer); 131 | yield return current; 132 | } 133 | 134 | break; 135 | } 136 | #endregion 137 | 138 | #region Replace 139 | case NotifyCollectionChangedAction.Replace: 140 | { 141 | var current = this; 142 | 143 | foreach (var oldItem in notification.OldItems) 144 | { 145 | current = current.Remove(oldItem, equalityComparer); 146 | yield return current; 147 | } 148 | 149 | foreach (var newItem in notification.NewItems) 150 | { 151 | current = current.Add(newItem); 152 | yield return current; 153 | } 154 | 155 | break; 156 | } 157 | #endregion 158 | 159 | #region default 160 | default: 161 | { 162 | var cleared = Clear(); 163 | 164 | yield return cleared; 165 | 166 | if (notification.Current.Count > 0) 167 | { 168 | foreach (var newItem in notification.Current) 169 | { 170 | cleared = cleared.Add(newItem); //TODO 171 | yield return cleared; 172 | } 173 | } 174 | 175 | break; 176 | } 177 | #endregion 178 | } 179 | } 180 | 181 | private int FindInsertionIndex(T item) 182 | { 183 | // TODO: Optimize, do a binary search or something. 184 | for (var newInsertionIndex = 0; newInsertionIndex < Current.Count; newInsertionIndex++) 185 | { 186 | if (Comparer.Compare(item, Current[newInsertionIndex]) < 0) 187 | return newInsertionIndex; 188 | } 189 | 190 | return Current.Count; 191 | } 192 | 193 | public int? Index { get; } 194 | 195 | public IComparer Comparer { get; } 196 | 197 | public new ImmutableList Current => (ImmutableList)base.Current; 198 | } 199 | } -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Notifications/SortedSetChangedNotification.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | using System.Collections.Specialized; 4 | 5 | namespace ExRam.ReactiveCollections 6 | { 7 | public sealed class SortedSetChangedNotification : CollectionChangedNotification, IIndexedCollectionChangedNotification 8 | { 9 | public static readonly SortedSetChangedNotification Reset = new (ImmutableSortedSet.Empty, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null); 10 | 11 | // ReSharper disable once SuggestBaseTypeForParameter 12 | public SortedSetChangedNotification(ImmutableSortedSet current, NotifyCollectionChangedAction action, IReadOnlyList oldItems, IReadOnlyList newItems, int? index) : base(current, action, oldItems, newItems) 13 | { 14 | Index = index; 15 | } 16 | 17 | public override ICollectionChangedNotification ToResetNotification() 18 | { 19 | return new SortedSetChangedNotification(Current, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null); 20 | } 21 | 22 | public int? Index { get; } 23 | 24 | public new ImmutableSortedSet Current => (ImmutableSortedSet)base.Current; 25 | 26 | internal SortedSetChangedNotification WithComparer(IComparer comparer) 27 | { 28 | return new(Current.WithComparer(comparer), Action, OldItems, NewItems, Index); 29 | } 30 | 31 | internal SortedSetChangedNotification Add(T value) 32 | { 33 | var current = Current; 34 | var newSet = current.Add(value); 35 | 36 | return newSet != current 37 | ? new(newSet, NotifyCollectionChangedAction.Add, ImmutableList.Empty, ImmutableList.Create(value), newSet.IndexOf(value)) 38 | : this; 39 | } 40 | 41 | internal SortedSetChangedNotification Clear() 42 | { 43 | var current = Current; 44 | 45 | return !current.IsEmpty 46 | ? new(current.Clear(), NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null) 47 | : this; 48 | } 49 | 50 | internal SortedSetChangedNotification Except(IEnumerable other) 51 | { 52 | var current = Current; 53 | var newSet = current.Except(other); 54 | 55 | return newSet != current 56 | ? new(newSet, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null) 57 | : this; 58 | } 59 | 60 | internal SortedSetChangedNotification Intersect(IEnumerable other) 61 | { 62 | var current = Current; 63 | var newSet = current.Intersect(other); 64 | 65 | return newSet != current 66 | ? new(newSet, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null) 67 | : this; 68 | } 69 | 70 | internal SortedSetChangedNotification Remove(T value) 71 | { 72 | var current = Current; 73 | var newSet = current.Remove(value); 74 | 75 | return newSet != current 76 | ? new(newSet, NotifyCollectionChangedAction.Remove, ImmutableList.Create(value), ImmutableList.Empty, Current.IndexOf(value)) 77 | : this; 78 | } 79 | 80 | internal SortedSetChangedNotification SymmetricExcept(IEnumerable other) 81 | { 82 | var current = Current; 83 | var newSet = current.SymmetricExcept(other); 84 | 85 | return newSet != current 86 | ? new(newSet, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null) 87 | : this; 88 | } 89 | 90 | internal SortedSetChangedNotification Union(IEnumerable other) 91 | { 92 | var current = Current; 93 | var newSet = current.Union(other); 94 | 95 | return newSet != current 96 | ? new(newSet, NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null) 97 | : this; 98 | } 99 | 100 | internal IEnumerable> Sort(ICollectionChangedNotification notification) 101 | { 102 | switch (notification.Action) 103 | { 104 | #region Add 105 | case NotifyCollectionChangedAction.Add: 106 | { 107 | var current = this; 108 | 109 | foreach (var newItem in notification.NewItems) 110 | { 111 | current = current.Add(newItem); 112 | 113 | yield return current; 114 | } 115 | 116 | break; 117 | } 118 | #endregion 119 | 120 | #region Remove 121 | case NotifyCollectionChangedAction.Remove: 122 | { 123 | var current = this; 124 | 125 | foreach (var oldItem in notification.OldItems) 126 | { 127 | current = current.Remove(oldItem); 128 | 129 | yield return current; 130 | } 131 | 132 | break; 133 | } 134 | #endregion 135 | 136 | #region Replace 137 | case NotifyCollectionChangedAction.Replace: 138 | { 139 | var current = this; 140 | 141 | foreach (var oldItem in notification.OldItems) 142 | { 143 | current = current.Remove(oldItem); 144 | 145 | yield return current; 146 | } 147 | 148 | foreach (var newItem in notification.NewItems) 149 | { 150 | current = current.Add(newItem); 151 | 152 | yield return current; 153 | } 154 | 155 | break; 156 | } 157 | #endregion 158 | 159 | #region default 160 | default: 161 | { 162 | var cleared = Clear(); 163 | 164 | yield return cleared; 165 | 166 | if (notification.Current.Count > 0) 167 | { 168 | foreach (var newItem in notification.Current) 169 | { 170 | cleared = cleared.Add(newItem); 171 | 172 | yield return cleared; 173 | } 174 | } 175 | 176 | break; 177 | } 178 | #endregion 179 | } 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ExRam.ReactiveCollections.Tests")] -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/ReactiveCollectionSources/DictionaryReactiveCollectionSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.Collections.Specialized; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Linq; 8 | 9 | namespace ExRam.ReactiveCollections 10 | { 11 | public class DictionaryReactiveCollectionSource : ReactiveCollectionSource>, 12 | IDictionary, 13 | IDictionary, 14 | IReadOnlyDictionary 15 | where TKey : notnull 16 | { 17 | public DictionaryReactiveCollectionSource() : this(EqualityComparer.Default, EqualityComparer.Default) 18 | { 19 | 20 | } 21 | 22 | public DictionaryReactiveCollectionSource(IEqualityComparer keyComparer) : this(keyComparer, EqualityComparer.Default) 23 | { 24 | 25 | } 26 | 27 | public DictionaryReactiveCollectionSource(IEqualityComparer keyComparer, IEqualityComparer valueComparer) : base( 28 | new DictionaryChangedNotification( 29 | ImmutableDictionary.Empty.WithComparers(keyComparer, valueComparer), 30 | NotifyCollectionChangedAction.Reset, 31 | ImmutableList>.Empty, 32 | ImmutableList>.Empty)) 33 | { 34 | } 35 | 36 | public void Add(TKey key, TValue value) => TryUpdate(notification => notification.Add(new KeyValuePair(key, value))); 37 | 38 | public void AddRange(IEnumerable values, Func keySelector) => AddRange(values.Select(x => new KeyValuePair(keySelector(x), x))); 39 | 40 | public void AddRange(IEnumerable> pairs) => TryUpdate(notification => notification.AddRange(pairs)); 41 | 42 | public void Clear() => TryUpdate(notification => notification.Clear()); 43 | 44 | public bool Contains(KeyValuePair pair) => Current.Contains(pair); 45 | 46 | public void Remove(TKey key) => TryUpdate(notification => notification.Remove(key)); 47 | 48 | public void RemoveRange(IEnumerable keys) => TryUpdate(notification => notification.RemoveRange(keys)); 49 | 50 | public void SetItem(TKey key, TValue value) => TryUpdate(notification => notification.SetItem(key, value)); 51 | 52 | public void SetItems(IEnumerable> items) => TryUpdate(notification => notification.SetItems(items)); 53 | 54 | public bool ContainsKey(TKey key) => Current.ContainsKey(key); 55 | 56 | public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => Current.TryGetValue(key, out value); 57 | 58 | public TValue this[TKey key] 59 | { 60 | get => Current[key]; 61 | set => SetItem(key, value); 62 | } 63 | 64 | public int Count => Current.Count; 65 | 66 | public IEnumerable Values => Current.Values; 67 | 68 | public IEnumerator> GetEnumerator() => Current.GetEnumerator(); 69 | 70 | private ImmutableDictionary Current => CurrentNotification.Current; 71 | 72 | #region IDictionary implementation 73 | void IDictionary.Add(TKey key, TValue value) 74 | { 75 | Add(key, value); 76 | } 77 | 78 | ICollection IDictionary.Keys => Current.Keys.ToList(); 79 | 80 | bool IDictionary.Remove(TKey key) 81 | { 82 | var oldList = Current; 83 | Remove(key); 84 | 85 | return Current != oldList; 86 | } 87 | 88 | ICollection IDictionary.Values => Current.Values.ToList(); 89 | #endregion 90 | 91 | #region ICollection> implementation 92 | void ICollection>.Add(KeyValuePair item) 93 | { 94 | Add(item.Key, item.Value); 95 | } 96 | 97 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) 98 | { 99 | ((ICollection>)Current).CopyTo(array, arrayIndex); 100 | } 101 | 102 | bool ICollection>.Remove(KeyValuePair item) 103 | { 104 | return ((IDictionary)this).Remove(item.Key); 105 | } 106 | 107 | void ICollection>.Clear() 108 | { 109 | Clear(); 110 | } 111 | 112 | bool ICollection>.IsReadOnly => false; 113 | 114 | #endregion 115 | 116 | #region IDictionary implementation 117 | void IDictionary.Add(object key, object? value) => throw new NotSupportedException(); 118 | 119 | bool IDictionary.Contains(object key) => ContainsKey((TKey)key); 120 | 121 | IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)Current).GetEnumerator(); 122 | 123 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 124 | 125 | bool IDictionary.IsFixedSize => false; 126 | 127 | bool IDictionary.IsReadOnly => false; 128 | 129 | ICollection IDictionary.Keys => Current.Keys.ToList(); 130 | 131 | void IDictionary.Remove(object key) => Remove((TKey)key); 132 | 133 | ICollection IDictionary.Values => Current.Values.ToList(); 134 | 135 | object? IDictionary.this[object key] 136 | { 137 | get => this[(TKey)key]; 138 | set => throw new NotSupportedException(); 139 | } 140 | 141 | void ICollection.CopyTo(Array array, int index) => ((IDictionary)this).CopyTo(array, index); 142 | #endregion 143 | 144 | #region ICollection implementation 145 | bool ICollection.IsSynchronized => false; 146 | 147 | object ICollection.SyncRoot => this; 148 | #endregion 149 | 150 | IEnumerable IReadOnlyDictionary.Keys => ((IDictionary)this).Keys; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/ReactiveCollectionSources/ListReactiveCollectionSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | 6 | namespace ExRam.ReactiveCollections 7 | { 8 | public class ListReactiveCollectionSource : 9 | ReactiveCollectionSource>, 10 | IList, 11 | IList 12 | { 13 | public ListReactiveCollectionSource() : this(ImmutableList.Empty) 14 | { 15 | } 16 | 17 | public ListReactiveCollectionSource(IEnumerable items) : base(ListChangedNotification.Reset) 18 | { 19 | if (!ReferenceEquals(items, ImmutableList.Empty)) 20 | AddRange(items); 21 | } 22 | 23 | public void Add(T item) => Insert(Current.Count, item); 24 | 25 | public void AddRange(IEnumerable items) => InsertRange(Current.Count, items); 26 | 27 | public void Clear() => TryUpdate(notification => notification.Clear()); 28 | 29 | public bool Contains(T item) => Current.Contains(item); 30 | 31 | public void CopyTo(T[] array, int arrayIndex) => Current.CopyTo(array, arrayIndex); 32 | 33 | public IEnumerator GetEnumerator() => Current.GetEnumerator(); 34 | 35 | public void InsertRange(int index, IEnumerable items) => TryUpdate(notification => notification.InsertRange(index, items)); 36 | 37 | public int IndexOf(T item) => Current.IndexOf(item); 38 | 39 | public void Insert(int index, T item) => TryUpdate(notification => notification.Insert(index, item)); 40 | 41 | public bool Remove(T item) => Remove(item, EqualityComparer.Default); 42 | 43 | public bool Remove(T item, IEqualityComparer equalityComparer) => TryUpdate(notification => notification.Remove(item, equalityComparer)); 44 | 45 | public void RemoveAll(Predicate match) => TryUpdate(notification => notification.RemoveAll(match)); 46 | 47 | public void RemoveAt(int index) => TryUpdate(notification => notification.RemoveAt(index)); 48 | 49 | public void RemoveRange(int index, int count) => TryUpdate(notification => notification.RemoveRange(index, count)); 50 | 51 | public void RemoveRange(IEnumerable items) => RemoveRange(items, EqualityComparer.Default); 52 | 53 | public void RemoveRange(IEnumerable items, IEqualityComparer equalityComparer) => TryUpdate(notification => notification.RemoveRange(items, equalityComparer)); 54 | 55 | public void Replace(T oldValue, T newValue) => Replace(oldValue, newValue, EqualityComparer.Default); 56 | 57 | public void Replace(T oldValue, T newValue, IEqualityComparer equalityComparer) 58 | { 59 | var index = Current.IndexOf(oldValue, 0, Count, equalityComparer); 60 | 61 | if (index > -1) 62 | SetItem(index, newValue); 63 | } 64 | 65 | public void Reverse() => Reverse(0, Count); 66 | 67 | public void Reverse(int index, int count) => TryUpdate(notification => notification.Reverse(index, count)); 68 | 69 | public void SetItem(int index, T value) => TryUpdate(notification => notification.SetItem(index, value)); 70 | 71 | public void Sort() => Sort(Comparer.Default); 72 | 73 | public void Sort(Comparison comparison) => Sort(comparison.ToComparer()); 74 | 75 | public void Sort(IComparer comparer) => Sort(0, Count, comparer); 76 | 77 | public void Sort(int index, int count, IComparer comparer) => TryUpdate(notification => notification.Sort(index, count, comparer)); 78 | 79 | public T this[int index] 80 | { 81 | get => Current[index]; 82 | set => SetItem(index, value); 83 | } 84 | 85 | public int Count => Current.Count; 86 | 87 | private ImmutableList Current => CurrentNotification.Current; 88 | 89 | #region Explicit IList implementation 90 | T IList.this[int index] 91 | { 92 | get => this[index]; 93 | set => SetItem(index, value); 94 | } 95 | 96 | bool ICollection.IsReadOnly => false; 97 | #endregion 98 | 99 | #region Explicit IList implementation 100 | int IList.Add(object? value) => throw new NotSupportedException(); 101 | 102 | bool IList.Contains(object? value) => IsCompatibleObject(value) && Contains((T)value!); 103 | 104 | int IList.IndexOf(object? value) => IsCompatibleObject(value) ? IndexOf((T)value!) : -1; 105 | 106 | void IList.Insert(int index, object? value) => throw new NotSupportedException(); 107 | 108 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 109 | 110 | bool IList.IsFixedSize => false; 111 | 112 | bool IList.IsReadOnly => false; 113 | 114 | void IList.Remove(object? value) => throw new NotSupportedException(); 115 | 116 | void IList.RemoveAt(int index) => RemoveAt(index); 117 | 118 | object? IList.this[int index] 119 | { 120 | get => this[index]; 121 | set => throw new NotSupportedException(); 122 | } 123 | 124 | void ICollection.CopyTo(Array array, int index) => CopyTo((T[])array, index); 125 | 126 | bool ICollection.IsSynchronized => false; 127 | 128 | object ICollection.SyncRoot => this; 129 | #endregion 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/ReactiveCollectionSources/ReactiveCollectionSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Subjects; 4 | 5 | namespace ExRam.ReactiveCollections 6 | { 7 | public abstract class ReactiveCollectionSource : IReactiveCollectionSource 8 | where TNotification : class, ICollectionChangedNotification 9 | { 10 | #region ReactiveCollectionImpl 11 | private sealed class ReactiveCollectionImpl : IReactiveCollection 12 | { 13 | private readonly IObservable _changes; 14 | 15 | public ReactiveCollectionImpl(IObservable subject) 16 | { 17 | _changes = subject 18 | .Normalize(); 19 | } 20 | 21 | IObservable IReactiveCollection.Changes => _changes; 22 | } 23 | #endregion 24 | 25 | private readonly BehaviorSubject _subject; 26 | 27 | protected ReactiveCollectionSource(TNotification initialNotification) 28 | { 29 | _subject = new (initialNotification); 30 | ReactiveCollection = new ReactiveCollectionImpl(_subject); 31 | } 32 | 33 | protected static bool IsCompatibleObject(object? value) 34 | { 35 | // Non-null values are fine. Only accept nulls if T is a class or Nullable. 36 | // Note that default(T) is not equal to null for value types except when T is Nullable. 37 | return value is T || value == null && default(T) == null; 38 | } 39 | 40 | protected bool TryUpdate(Func transformation) 41 | { 42 | var currentValue = _subject.Value; 43 | var newNotification = transformation(currentValue); 44 | 45 | if (!ReferenceEquals(currentValue, newNotification)) 46 | { 47 | _subject.OnNext(newNotification); 48 | return true; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | public IReactiveCollection ReactiveCollection { get; } 55 | 56 | protected TNotification CurrentNotification => _subject.Value; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/ReactiveCollectionSources/SortedListReactiveCollectionSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace ExRam.ReactiveCollections 6 | { 7 | public class SortedListReactiveCollectionSource : 8 | ReactiveCollectionSource>, 9 | ICollection, 10 | ICollection 11 | { 12 | public SortedListReactiveCollectionSource() : this(Comparer.Default) 13 | { 14 | } 15 | 16 | public SortedListReactiveCollectionSource(IComparer comparer) : base(SortedListChangedNotification.Reset.WithComparer(comparer)) 17 | { 18 | } 19 | 20 | public void Add(T item) => TryUpdate(notification => notification.Add(item)); 21 | 22 | public void AddRange(IEnumerable items) 23 | { 24 | foreach (var item in items) 25 | { 26 | Add(item); 27 | } 28 | } 29 | 30 | public void Clear() => TryUpdate(notification => notification.Clear()); 31 | 32 | public bool Contains(T item) => CurrentNotification.Current.Contains(item); 33 | 34 | public void CopyTo(T[] array, int arrayIndex) => CurrentNotification.Current.CopyTo(array, arrayIndex); 35 | 36 | public IEnumerator GetEnumerator() => CurrentNotification.Current.GetEnumerator(); 37 | 38 | public int IndexOf(T item) => CurrentNotification.Current.IndexOf(item); 39 | 40 | public bool Remove(T item) => Remove(item, EqualityComparer.Default); 41 | 42 | public bool Remove(T item, IEqualityComparer equalityComparer) => TryUpdate(notification => notification.Remove(item, equalityComparer)); 43 | 44 | public void RemoveAll(Predicate match) => TryUpdate(notification => notification.RemoveAll(match)); 45 | 46 | public void RemoveAt(int index) => TryUpdate(notification => notification.RemoveAt(index)); 47 | 48 | public void RemoveRange(int index, int count) => TryUpdate(notification => notification.RemoveRange(index, count)); 49 | 50 | public void RemoveRange(IEnumerable items) => RemoveRange(items, EqualityComparer.Default); 51 | 52 | public void RemoveRange(IEnumerable items, IEqualityComparer itemsEqualityComparer) => TryUpdate(notification => notification.RemoveRange(items, itemsEqualityComparer)); 53 | 54 | public void Replace(T oldValue, T newValue) => Replace(oldValue, newValue, EqualityComparer.Default); 55 | 56 | public void Replace(T oldValue, T newValue, IEqualityComparer equalityComparer) 57 | { 58 | Remove(oldValue, equalityComparer); 59 | Add(newValue); 60 | } 61 | 62 | public int Count => CurrentNotification.Current.Count; 63 | 64 | public T this[int index] => CurrentNotification.Current[index]; 65 | 66 | #region Explicit ICollection implementation 67 | bool ICollection.IsReadOnly => false; 68 | 69 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 70 | 71 | void ICollection.CopyTo(Array array, int index) => CopyTo((T[])array, index); 72 | 73 | bool ICollection.IsSynchronized => false; 74 | 75 | object ICollection.SyncRoot => this; 76 | #endregion 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ExRam.ReactiveCollections/ReactiveCollectionSources/SortedSetReactiveCollectionSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.Collections.Specialized; 6 | 7 | namespace ExRam.ReactiveCollections 8 | { 9 | public class SortedSetReactiveCollectionSource : 10 | ReactiveCollectionSource>, 11 | IList, 12 | IList, 13 | ISet 14 | { 15 | public SortedSetReactiveCollectionSource() : this(Comparer.Default) 16 | { 17 | } 18 | 19 | public SortedSetReactiveCollectionSource(IComparer comparer) : base(new SortedSetChangedNotification(ImmutableSortedSet.Create(comparer), NotifyCollectionChangedAction.Reset, ImmutableList.Empty, ImmutableList.Empty, null)) 20 | { 21 | 22 | } 23 | 24 | public void Add(T value) => TryUpdate(notification => notification.Add(value)); 25 | 26 | public void AddRange(IEnumerable items) 27 | { 28 | foreach (var item in items) 29 | { 30 | Add(item); 31 | } 32 | } 33 | 34 | public void Clear() => TryUpdate(notification => notification.Clear()); 35 | 36 | public void Except(IEnumerable other) => TryUpdate(notification => notification.Except(other)); 37 | 38 | public void Intersect(IEnumerable other) => TryUpdate(notification => notification.Intersect(other)); 39 | 40 | public bool IsProperSubsetOf(IEnumerable other) => Current.IsProperSubsetOf(other); 41 | 42 | public bool IsProperSupersetOf(IEnumerable other) => Current.IsProperSupersetOf(other); 43 | 44 | public bool IsSubsetOf(IEnumerable other) => Current.IsSubsetOf(other); 45 | 46 | public bool IsSupersetOf(IEnumerable other) => Current.IsSupersetOf(other); 47 | 48 | public bool Overlaps(IEnumerable other) => Current.Overlaps(other); 49 | 50 | public void Remove(T value) => TryUpdate(notification => notification.Remove(value)); 51 | 52 | public bool SetEquals(IEnumerable other) => Current.SetEquals(other); 53 | 54 | public void SymmetricExcept(IEnumerable other) => TryUpdate(notification => notification.SymmetricExcept(other)); 55 | 56 | public bool TryGetValue(T equalValue, out T actualValue) => Current.TryGetValue(equalValue, out actualValue); 57 | 58 | public void Union(IEnumerable other) => TryUpdate(notification => notification.Union(other)); 59 | 60 | private ImmutableSortedSet Current => CurrentNotification.Current; 61 | 62 | #region IList implementation 63 | public int IndexOf(T item) => Current.IndexOf(item); 64 | 65 | void IList.Insert(int index, T item) => throw new NotSupportedException(); 66 | 67 | void IList.RemoveAt(int index) => throw new NotSupportedException(); 68 | 69 | public T this[int index] 70 | { 71 | get => Current[index]; 72 | set => throw new NotSupportedException(); 73 | } 74 | #endregion 75 | 76 | #region ICollection implementation 77 | public bool Contains(T item) => Current.Contains(item); 78 | 79 | public void CopyTo(T[] array, int arrayIndex) => ((ICollection)Current).CopyTo(array, arrayIndex); 80 | 81 | public int Count => Current.Count; 82 | 83 | bool ICollection.IsReadOnly => false; 84 | 85 | bool ICollection.Remove(T item) 86 | { 87 | var oldList = Current; 88 | Remove(item); 89 | 90 | return Current != oldList; 91 | } 92 | #endregion 93 | 94 | #region IEnumerable implemenation 95 | public IEnumerator GetEnumerator() 96 | { 97 | return Current.GetEnumerator(); 98 | } 99 | #endregion 100 | 101 | #region IList implementation 102 | int IList.Add(object? value) => throw new NotSupportedException(); 103 | 104 | void IList.Clear() => Clear(); 105 | 106 | bool IList.Contains(object? value) => Contains((T)value!); 107 | 108 | int IList.IndexOf(object? value) => IndexOf((T)value!); 109 | 110 | void IList.Insert(int index, object? value) => throw new NotSupportedException(); 111 | 112 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 113 | 114 | bool IList.IsFixedSize => false; 115 | 116 | bool IList.IsReadOnly => false; 117 | 118 | void IList.Remove(object? value) => throw new NotSupportedException(); 119 | 120 | void IList.RemoveAt(int index) => throw new NotSupportedException(); 121 | 122 | object? IList.this[int index] 123 | { 124 | get => this[index]; 125 | set => throw new NotSupportedException(); 126 | } 127 | 128 | void ICollection.CopyTo(Array array, int index) 129 | { 130 | CopyTo((T[])array, index); 131 | } 132 | 133 | bool ICollection.IsSynchronized => false; 134 | 135 | object ICollection.SyncRoot => this; 136 | #endregion 137 | 138 | #region ISet implementation 139 | void ISet.ExceptWith(IEnumerable other) => throw new NotSupportedException(); 140 | 141 | void ISet.IntersectWith(IEnumerable other) => throw new NotSupportedException(); 142 | 143 | void ISet.SymmetricExceptWith(IEnumerable other) => throw new NotSupportedException(); 144 | 145 | void ISet.UnionWith(IEnumerable other) => throw new NotSupportedException(); 146 | 147 | bool ISet.Add(T item) => throw new NotSupportedException(); 148 | #endregion 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ExRam GmbH & Co. KG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ExRam.ReactiveCollections 2 | ========================= 3 | 4 | To explain the idea behind ReactiveCollections, let's look at this completely ficticious conversation between Alice and Bob: 5 | 6 | Alice: Well, what's ReactiveCollections ? 7 | 8 | Bob: Glad you ask. Do you know Reactive Extensions ? 9 | 10 | Alice: Of course, it's a framework to model and compose asynchronous events. Great stuff by the way. 11 | 12 | Bob: It is. But I like to see Rx as a way to model change over time. 13 | 14 | Alice: What does change? 15 | 16 | Bob: Whatever you like, for example the state of some variable in an object. 17 | 18 | Alice: I see. So, ReactiveCollections uses Rx to notify about the state of some collection? Isn't that just like an ObservableCollection? There's a CollectionChanged event on it, couldn't we just use that? 19 | 20 | Bob: Yes and no. See, an ObservableCollection still uses an internal mutable state to represent its current content. 21 | 22 | Alice: Why is that bad? 23 | 24 | Bob: You have to synchronize access to it. You have to lock the collection every time you read or write to it. 25 | 26 | Alice: Oh that's ok, I just put lock(items) everywhere and I'll be fine. 27 | 28 | Bob: You could just do that. But blocking is so uncool in times of async/await. Moreover, are you really going to block your UI-thread? And why wouldn't you want to allow mutiple readers. 29 | 30 | Alice: There's always ReaderWriterLock... 31 | 32 | Bob: ...and it's fun to share ReaderWriterLock instances between readers and writers throughout your whole application. 33 | 34 | Alice: No it's not. So, I guess that's where Immutable Collections come into play. 35 | 36 | Bob: Yes! While we model change of state in a collection with Rx, Immutable Collections model the state itself! And whenever you got hold of some current state of some collection, you can do with it what you want - it's immutable, you cannot break anything. 37 | 38 | Alice: So, let's say I need a simple list in my application... 39 | 40 | Bob: ...just create one: 41 | 42 | var list = new ListReactiveCollectionSource(). 43 | 44 | Alice: ListReactiveCollectionSource? What's Source? 45 | 46 | Bob: It's like TaskCompletionSource or CancellationTokenSource. You basically have a ReactiveCollectionSource that you use to modify the collection... 47 | 48 | Alice: ...and like a CancellationTokenSource, ListReactiveCollectionSource has a property that I can give out to consumers of the list? 49 | 50 | Bob: Exactly: 51 | 52 | var list = new ListReactiveCollectionSource(); 53 | var rc = list.ReactiveCollection; 54 | 55 | Alice: As I see, rc implements an IObservable of something. What happens if I subscribe to it? 56 | 57 | Bob: Your observer will be notified of the current state of the list immediately, which is an ImmutableList in this case. 58 | 59 | Alice: Cool. And since it's just an asynchronous sequence of ImmutableLists, I may as well just skip the first state if I'm only interested in the second... 60 | 61 | Bob ...yes... 62 | 63 | Alice ...or take the first 5 changes in a list... 64 | 65 | Bob ...yes... 66 | 67 | Alice ...and await it... 68 | 69 | Bob ...absolutely. 70 | 71 | Alice I see there's more in Immutable Collections: ImmutableDictionary, ImmutableSortedList... 72 | 73 | Bob: ...and there's corresponding clases in ReactiveCollections. 74 | 75 | Alice: What if I wanted to filter, project or sort such a ReactiveCollection. You know, like in LINQ. 76 | 77 | Bob: You can! 78 | 79 | Alice: So, that's all good, but at the end of the day, I have to display my filtered, projected and sorted collection in my cool new app. But my UI only can bind to a good old ObservableCollection. 80 | 81 | Bob: Just call 82 | 83 | var oc = list.ToObservableCollection 84 | 85 | to your ReactiveCollection and you'll be there. 86 | 87 | Alice: Now I want to clone this repository and start playing with it. 88 | 89 | Bob: Go ahead, thanks for your interest! 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.1" 3 | } --------------------------------------------------------------------------------