Compare commits

..

No commits in common. "master" and "3.1.1" have entirely different histories.

32 changed files with 818 additions and 1641 deletions

View File

@ -1,41 +0,0 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
# Visual Studio Spell checker configs (https://learn.microsoft.com/en-us/visualstudio/ide/text-spell-checker?view=vs-2022#how-to-customize-the-spell-checker)
spelling_exclusion_path = ./exclusion.dic
[*.cs]
indent_size = 4
charset = utf-8-bom
end_of_line = unset
# Solution files
[*.{sln,slnx}]
end_of_line = unset
# MSBuild project files
[*.{csproj,props,targets}]
end_of_line = unset
# Xml config files
[*.{ruleset,config,nuspec,resx,runsettings,DotSettings}]
end_of_line = unset
[*{_AssemblyInfo.cs,.notsupported.cs}]
generated_code = true
# C# code style settings
[*.{cs}]
dotnet_diagnostic.IDE0044.severity = none # IDE0044: Make field readonly
# https://stackoverflow.com/questions/79195382/how-to-disable-fading-unused-methods-in-visual-studio-2022-17-12-0
dotnet_diagnostic.IDE0051.severity = none # IDE0051: Remove unused private member
dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure

View File

@ -5,8 +5,3 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" # Check for updates to GitHub Actions every week interval: "weekly" # Check for updates to GitHub Actions every week
ignore:
# I just want update action when major/minor version is updated. patch updates are too noisy.
- dependency-name: '*'
update-types:
- version-update:semver-patch

View File

@ -10,12 +10,10 @@ on:
jobs: jobs:
build-dotnet: build-dotnet:
permissions: runs-on: ubuntu-latest
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: Cysharp/Actions/.github/actions/checkout@main - uses: actions/checkout@v4
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main - uses: Cysharp/Actions/.github/actions/setup-dotnet@main
- run: dotnet build -c Debug - run: dotnet build -c Debug
- run: dotnet test -c Debug --no-build - run: dotnet test -c Debug --no-build

View File

@ -14,12 +14,10 @@ on:
jobs: jobs:
build-dotnet: build-dotnet:
permissions: runs-on: ubuntu-latest
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: Cysharp/Actions/.github/actions/checkout@main - uses: actions/checkout@v4
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main - uses: Cysharp/Actions/.github/actions/setup-dotnet@main
# build and pack # build and pack
- run: dotnet build -c Release -p:Version=${{ inputs.tag }} - run: dotnet build -c Release -p:Version=${{ inputs.tag }}
@ -35,8 +33,6 @@ jobs:
# release # release
create-release: create-release:
needs: [build-dotnet] needs: [build-dotnet]
permissions:
contents: write
uses: Cysharp/Actions/.github/workflows/create-release.yaml@main uses: Cysharp/Actions/.github/workflows/create-release.yaml@main
with: with:
commit-id: ${{ github.sha }} commit-id: ${{ github.sha }}

View File

@ -7,6 +7,4 @@ on:
jobs: jobs:
detect: detect:
permissions:
contents: read
uses: Cysharp/Actions/.github/workflows/prevent-github-change.yaml@main uses: Cysharp/Actions/.github/workflows/prevent-github-change.yaml@main

View File

@ -7,8 +7,4 @@ on:
jobs: jobs:
stale: stale:
permissions:
contents: read
pull-requests: write
issues: write
uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main

View File

@ -50,12 +50,12 @@ ObservableCollections has not just a simple list, there are many more data struc
If you want to handle each change event with Rx, you can monitor it with the following method by combining it with [R3](https://github.com/Cysharp/R3): If you want to handle each change event with Rx, you can monitor it with the following method by combining it with [R3](https://github.com/Cysharp/R3):
```csharp ```csharp
Observable<CollectionChangedEvent<T>> IObservableCollection<T>.ObserveChanged()
Observable<CollectionAddEvent<T>> IObservableCollection<T>.ObserveAdd() Observable<CollectionAddEvent<T>> IObservableCollection<T>.ObserveAdd()
Observable<CollectionRemoveEvent<T>> IObservableCollection<T>.ObserveRemove() Observable<CollectionRemoveEvent<T>> IObservableCollection<T>.ObserveRemove()
Observable<CollectionReplaceEvent<T>> IObservableCollection<T>.ObserveReplace() Observable<CollectionReplaceEvent<T>> IObservableCollection<T>.ObserveReplace()
Observable<CollectionMoveEvent<T>> IObservableCollection<T>.ObserveMove() Observable<CollectionMoveEvent<T>> IObservableCollection<T>.ObserveMove()
Observable<CollectionResetEvent<T>> IObservableCollection<T>.ObserveReset() Observable<CollectionResetEvent<T>> IObservableCollection<T>.ObserveReset()
Observable<CollectionResetEvent<T>> IObservableCollection<T>.ObserveReset()
Observable<Unit> IObservableCollection<T>.ObserveClear<T>() Observable<Unit> IObservableCollection<T>.ObserveClear<T>()
Observable<(int Index, int Count)> IObservableCollection<T>.ObserveReverse<T>() Observable<(int Index, int Count)> IObservableCollection<T>.ObserveReverse<T>()
Observable<(int Index, int Count, IComparer<T>? Comparer)> IObservableCollection<T>.ObserveSort<T>() Observable<(int Index, int Count, IComparer<T>? Comparer)> IObservableCollection<T>.ObserveSort<T>()
@ -237,7 +237,7 @@ class DescendantComaprer : IComparer<int>
Reactive Extensions with R3 Reactive Extensions with R3
--- ---
Once the R3 extension package is installed, you can subscribe to `ObserveChanged`, `ObserveAdd`, `ObserveRemove`, `ObserveReplace`, `ObserveMove`, `ObserveReset`, `ObserveClear`, `ObserveReverse`, `ObserveSort`, `ObserveCounteChanged` events as Rx, allowing you to compose events individually. Once the R3 extension package is installed, you can subscribe to `ObserveAdd`, `ObserveRemove`, `ObserveReplace`, `ObserveMove`, `ObserveReset`, `ObserveClear`, `ObserveReverse`, `ObserveSort` events as Rx, allowing you to compose events individually.
> dotnet add package [ObservableCollections.R3](https://www.nuget.org/packages/ObservableCollections.R3) > dotnet add package [ObservableCollections.R3](https://www.nuget.org/packages/ObservableCollections.R3)
@ -259,8 +259,6 @@ list.AddRange(new[] { 10, 20, 30 });
Note that `ObserveReset` is used to subscribe to Clear, Reverse, and Sort operations in bulk. Note that `ObserveReset` is used to subscribe to Clear, Reverse, and Sort operations in bulk.
In addition to `IObservableCollection<T>`, there is also a subscription event for `ISynchronizedView<T, TView>`. In the case of View, `ObserveRejected` is also added.
Since it is not supported by dotnet/reactive, please use the Rx library [R3](https://github.com/Cysharp/R3). Since it is not supported by dotnet/reactive, please use the Rx library [R3](https://github.com/Cysharp/R3).
Blazor Blazor
@ -319,7 +317,7 @@ Because of data binding in WPF, it is important that the collection is Observabl
// WPF simple sample. // WPF simple sample.
ObservableList<int> list; ObservableList<int> list;
public NotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; set; } public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; set; }
public MainWindow() public MainWindow()
{ {
@ -368,8 +366,8 @@ public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T ori
public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView> public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
{ {
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter); INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher); INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
} }
``` ```
@ -443,7 +441,7 @@ public class SampleScript : MonoBehaviour
public Button prefab; public Button prefab;
public GameObject root; public GameObject root;
ObservableRingBuffer<int> collection; ObservableRingBuffer<int> collection;
ISynchronizedView<int, GameObject> view; ISynchronizedView<GameObject> view;
void Start() void Start()
{ {
@ -461,10 +459,10 @@ public class SampleScript : MonoBehaviour
view.ViewChanged += View_ViewChanged; view.ViewChanged += View_ViewChanged;
} }
void View_ViewChanged(in SynchronizedViewChangedEventArgs<int, GameObject> eventArgs) void View_ViewChanged(in SynchronizedViewChangedEventArgs<int, string> eventArgs)
{ {
// hook remove event // hook remove event
if (eventArgs.Action == NotifyCollectionChangedAction.Remove) if (NotifyCollectionChangedAction.Remove)
{ {
GameObject.Destroy(eventArgs.OldItem.View); GameObject.Destroy(eventArgs.OldItem.View);
} }
@ -486,7 +484,7 @@ ObservableCollections provides these collections.
```csharp ```csharp
class ObservableList<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>, IReadOnlyObservableList<T> class ObservableList<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>, IReadOnlyObservableList<T>
class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IObservableCollection<KeyValuePair<TKey, TValue>>, IReadOnlyObservableDictionary<TKey, TValue> where TKey : notnull class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IObservableCollection<KeyValuePair<TKey, TValue>> where TKey : notnull
class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T> where T : notnull class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T> where T : notnull
class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T> class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T> class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
@ -554,7 +552,7 @@ public enum RejectedViewChangedAction
public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisposable public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisposable
{ {
object SyncRoot { get; } object SyncRoot { get; }
ISynchronizedViewFilter<T, TView> Filter { get; } ISynchronizedViewFilter<T> Filter { get; }
IEnumerable<(T Value, TView View)> Filtered { get; } IEnumerable<(T Value, TView View)> Filtered { get; }
IEnumerable<(T Value, TView View)> Unfiltered { get; } IEnumerable<(T Value, TView View)> Unfiltered { get; }
int UnfilteredCount { get; } int UnfilteredCount { get; }
@ -563,11 +561,11 @@ public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisp
event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; // int index, int oldIndex(when RejectedViewChangedAction is Move) event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; // int index, int oldIndex(when RejectedViewChangedAction is Move)
event Action<NotifyCollectionChangedAction>? CollectionStateChanged; event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
void AttachFilter(ISynchronizedViewFilter<T, TView> filter); void AttachFilter(ISynchronizedViewFilter<T> filter);
void ResetFilter(); void ResetFilter();
ISynchronizedViewList<TView> ToViewList(); ISynchronizedViewList<TView> ToViewList();
NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(); INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
} }
``` ```
@ -612,9 +610,9 @@ When `IsReverse` is true, you need to use `Index` and `Count`. When `IsSort` is
For Filter, you can either create one that implements this interface or generate one from a lambda expression using extension methods. For Filter, you can either create one that implements this interface or generate one from a lambda expression using extension methods.
```csharp ```csharp
public interface ISynchronizedViewFilter<T, TView> public interface ISynchronizedViewFilter<T>
{ {
bool IsMatch(T value, TView view); bool IsMatch(T value);
} }
public static class SynchronizedViewExtensions public static class SynchronizedViewExtensions
@ -622,10 +620,6 @@ public static class SynchronizedViewExtensions
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, bool> filter) public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, bool> filter)
{ {
} }
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, TView, bool> filter)
{
}
} }
``` ```
@ -636,10 +630,10 @@ public sealed partial class ObservableList<T>
{ {
public IWritableSynchronizedView<T, TView> CreateWritableView<TView>(Func<T, TView> transform); public IWritableSynchronizedView<T, TView> CreateWritableView<TView>(Func<T, TView> transform);
public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(); public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged();
public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter); public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter);
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher, WritableViewChangedEventHandler<T, TView>? converter); public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher, WritableViewChangedEventHandler<T, TView>? converter);
} }
public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T originalValue, ref bool setValue); public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T originalValue, ref bool setValue);
@ -649,11 +643,6 @@ public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TVie
(T Value, TView View) GetAt(int index); (T Value, TView View) GetAt(int index);
void SetViewAt(int index, TView view); void SetViewAt(int index, TView view);
void SetToSourceCollection(int index, T value); void SetToSourceCollection(int index, T value);
void AddToSourceCollection(T value);
void InsertIntoSourceCollection(int index, T value);
bool RemoveFromSourceCollection(T value);
void RemoveAtSourceCollection(int index);
void ClearSourceCollection();
IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter); IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter); INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher); INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
@ -682,18 +671,9 @@ public interface ISynchronizedViewList<out TView> : IReadOnlyList<TView>, IDispo
{ {
} }
// Obsolete for public use public interface INotifyCollectionChangedSynchronizedViewList<out TView> : ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
public interface INotifyCollectionChangedSynchronizedViewList<TView> : IList<TView>, IList, ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
{ {
} }
public abstract class NotifyCollectionChangedSynchronizedViewList<TView> :
INotifyCollectionChangedSynchronizedViewList<TView>,
IWritableSynchronizedViewList<TView>,
IList<TView>,
IList
{
}
``` ```
License License

View File

@ -17,7 +17,7 @@ var list = new ObservableList<Person>()
var view = list.CreateWritableView(x => x.Name); var view = list.CreateWritableView(x => x.Name);
view.AttachFilter(x => x.Age >= 20); view.AttachFilter(x => x.Age >= 20);
var bindable = view.ToWritableNotifyCollectionChanged((string? newView, Person original, ref bool setValue) => IList<string?> bindable = view.ToWritableNotifyCollectionChanged((string? newView, Person original, ref bool setValue) =>
{ {
if (setValue) if (setValue)
{ {
@ -39,11 +39,6 @@ var bindable = view.ToWritableNotifyCollectionChanged((string? newView, Person o
} }
}); });
list.Clear();
list.Add(new() { Age = 99, Name = "tako" });
// bindable[0] = "takoyaki"; // bindable[0] = "takoyaki";
foreach (var item in view) foreach (var item in view)

View File

@ -5,62 +5,29 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp" xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Height="800" Width="800"> Title="MainWindow" Height="450" Width="800">
<Grid> <!--<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition />
<ColumnDefinition Width="*" /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- original --> <ListView ItemsSource="{Binding ItemsView}"></ListView>
<StackPanel Grid.Row="0" Grid.ColumnSpan="2" Orientation="Vertical">
<ListBox ItemsSource="{Binding ItemsView}" /> <Button Grid.Column="1" Click="Button_Click">Insert</Button>
<Button Content="Add" Command="{Binding AddCommand}" />
<Button Content="AddRange" Command="{Binding AddRangeCommand}" />
<Button Content="Insert" Command="{Binding InsertAtRandomCommand}" />
<Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" />
<Button Content="RemoveRange" Command="{Binding RemoveRangeCommand}" />
<Button Content="Clear" Command="{Binding ClearCommand}" />
<Button Content="Reverse" Command="{Binding ReverseCommand}" />
<Button Content="Sort" Command="{Binding SortCommand}" />
</StackPanel>
<!-- Upper left (NotWritable, NonFilter) --> </Grid>-->
<GroupBox Grid.Row="1" Grid.Column="0" Header="NotWritable, NonFilter"> <StackPanel>
<StackPanel Orientation="Vertical"> <ListBox ItemsSource="{Binding ItemsView}" />
<DataGrid ItemsSource="{Binding NotWritableNonFilterView}" /> <Button Content="Add" Command="{Binding AddCommand}" />
</StackPanel> <Button Content="AddRange" Command="{Binding AddRangeCommand}" />
</GroupBox> <Button Content="Insert" Command="{Binding InsertAtRandomCommand}" />
<Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" />
<!-- Upper right (Writable, NonFilter) --> <Button Content="RemoveRange" Command="{Binding RemoveRangeCommand}" />
<GroupBox Grid.Row="1" Grid.Column="1" Header="Writable, NonFilter"> <Button Content="Clear" Command="{Binding ClearCommand}" />
<StackPanel Orientation="Vertical"> <Button Content="Reverse" Command="{Binding ReverseCommand}" />
<DataGrid ItemsSource="{Binding WritableNonFilterPersonView}" /> <Button Content="Sort" Command="{Binding SortCommand}" />
</StackPanel> <Button Content="AttachFilter" Command="{Binding AttachFilterCommand}" />
</GroupBox> <Button Content="ResetFilter" Command="{Binding ResetFilterCommand}" />
</StackPanel>
<!-- Lower left (NotWritable, Filter) --> </Window>
<GroupBox Grid.Row="2" Grid.Column="0" Header="NotWritable, Filter">
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding NotWritableFilterView}" />
<Button Content="AttachFilter" Command="{Binding AttachFilterCommand2}" />
<Button Content="ResetFilter" Command="{Binding ResetFilterCommand2}" />
</StackPanel>
</GroupBox>
<!-- Lower right (Writable, Filter) -->
<GroupBox Grid.Row="2" Grid.Column="1" Header="Writable, Filter">
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding WritableFilterPersonView}" />
<Button Content="AttachFilter" Command="{Binding AttachFilterCommand3}" />
<Button Content="ResetFilter" Command="{Binding ResetFilterCommand3}" />
</StackPanel>
</GroupBox>
</Grid>
</Window>

View File

@ -87,18 +87,6 @@ namespace WpfApp
public ReactiveCommand<Unit> AttachFilterCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> AttachFilterCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ResetFilterCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> ResetFilterCommand { get; } = new ReactiveCommand<Unit>();
private ObservableList<Person> SourceList { get; } = [];
private IWritableSynchronizedView<Person, Person> writableFilter;
private ISynchronizedView<Person, Person> notWritableFilter;
public NotifyCollectionChangedSynchronizedViewList<Person> NotWritableNonFilterView { get; }
public NotifyCollectionChangedSynchronizedViewList<Person> NotWritableFilterView { get; }
public NotifyCollectionChangedSynchronizedViewList<Person> WritableNonFilterPersonView { get; }
public NotifyCollectionChangedSynchronizedViewList<Person> WritableFilterPersonView { get; }
public ReactiveCommand<Unit> AttachFilterCommand2 { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ResetFilterCommand2 { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> AttachFilterCommand3 { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ResetFilterCommand3 { get; } = new ReactiveCommand<Unit>();
public ViewModel() public ViewModel()
{ {
observableList.Add(1); observableList.Add(1);
@ -168,97 +156,8 @@ namespace WpfApp
{ {
view.ResetFilter(); view.ResetFilter();
}); });
SourceList.Add(new() { Name = "a", Age = 1 });
SourceList.Add(new() { Name = "b", Age = 2 });
SourceList.Add(new() { Name = "c", Age = 3 });
SourceList.Add(new() { Name = "d", Age = 4 });
//NotWritable, NonFilter
NotWritableNonFilterView = SourceList.ToNotifyCollectionChanged();
//NotWritable, Filter
notWritableFilter = SourceList.CreateView(x => x);
NotWritableFilterView = notWritableFilter.ToNotifyCollectionChanged();
//Writable, NonFilter
WritableNonFilterPersonView = SourceList.ToWritableNotifyCollectionChanged();
//WritableNonFilterPersonView = SourceList.ToWritableNotifyCollectionChanged(x => x, (Person newView, Person original, ref bool setValue) =>
//{
// if (setValue)
// {
// // default setValue == true is Set operation
// original.Name = newView.Name;
// original.Age = newView.Age;
// // You can modify setValue to false, it does not set original collection to new value.
// // For mutable reference types, when there is only a single,
// // bound View and to avoid recreating the View, setting false is effective.
// // Otherwise, keeping it true will set the value in the original collection as well,
// // and change notifications will be sent to lower-level Views(the delegate for View generation will also be called anew).
// setValue = false;
// return original;
// }
// else
// {
// // default setValue == false is Add operation
// return new Person { Age = newView.Age, Name = newView.Name };
// }
//}, null);
//Writable, Filter
writableFilter = SourceList.CreateWritableView(x => x);
WritableFilterPersonView = writableFilter.ToWritableNotifyCollectionChanged();
//WritableFilterPersonView = writableFilter.ToWritableNotifyCollectionChanged((Person newView, Person original, ref bool setValue) =>
//{
// if (setValue)
// {
// // default setValue == true is Set operation
// original.Name = newView.Name;
// original.Age = newView.Age;
// // You can modify setValue to false, it does not set original collection to new value.
// // For mutable reference types, when there is only a single,
// // bound View and to avoid recreating the View, setting false is effective.
// // Otherwise, keeping it true will set the value in the original collection as well,
// // and change notifications will be sent to lower-level Views(the delegate for View generation will also be called anew).
// setValue = false;
// return original;
// }
// else
// {
// // default setValue == false is Add operation
// return new Person { Age = newView.Age, Name = newView.Name };
// }
//});
AttachFilterCommand2.Subscribe(_ =>
{
notWritableFilter.AttachFilter(x => x.Age % 2 == 0);
});
ResetFilterCommand2.Subscribe(_ =>
{
notWritableFilter.ResetFilter();
});
AttachFilterCommand3.Subscribe(_ =>
{
writableFilter.AttachFilter(x => x.Age % 2 == 0);
});
ResetFilterCommand3.Subscribe(_ =>
{
writableFilter.ResetFilter();
});
} }
} }
public class Person
{
public int? Age { get; set; }
public string? Name { get; set; }
}
public class WpfDispatcherCollection(Dispatcher dispatcher) : ICollectionEventDispatcher public class WpfDispatcherCollection(Dispatcher dispatcher) : ICollectionEventDispatcher
{ {

View File

@ -1,509 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.Tracing;
using System.Runtime.InteropServices;
using System.Threading;
using R3;
namespace ObservableCollections;
[StructLayout(LayoutKind.Auto)]
public readonly record struct ViewChangedEvent<T, TView>
{
public readonly NotifyCollectionChangedAction Action;
public readonly (T Value, TView View) NewItem;
public readonly (T Value, TView View) OldItem;
public readonly int NewStartingIndex;
public readonly int OldStartingIndex;
public readonly SortOperation<T> SortOperation;
public ViewChangedEvent(NotifyCollectionChangedAction action, (T, TView) newItem, (T, TView) oldItem, int newStartingIndex, int oldStartingIndex, SortOperation<T> sortOperation)
{
Action = action;
NewItem = newItem;
OldItem = oldItem;
NewStartingIndex = newStartingIndex;
OldStartingIndex = oldStartingIndex;
SortOperation = sortOperation;
}
}
[StructLayout(LayoutKind.Auto)]
public readonly record struct RejectedViewChangedEvent
{
public readonly RejectedViewChangedAction Action;
public readonly int NewIndex;
public readonly int OldIndex;
public RejectedViewChangedEvent(RejectedViewChangedAction action, int newIndex, int oldIndex)
{
Action = action;
NewIndex = newIndex;
OldIndex = oldIndex;
}
}
public static partial class ObservableCollectionR3Extensions
{
public static Observable<RejectedViewChangedEvent> ObserveRejected<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewRejected<T, TView>(source, cancellationToken);
}
public static Observable<ViewChangedEvent<T, TView>> ObserveChanged<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewChanged<T, TView>(source, cancellationToken);
}
public static Observable<CollectionAddEvent<(T Value, TView View)>> ObserveAdd<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewAdd<T, TView>(source, cancellationToken);
}
public static Observable<CollectionRemoveEvent<(T Value, TView View)>> ObserveRemove<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewRemove<T, TView>(source, cancellationToken);
}
public static Observable<CollectionReplaceEvent<(T Value, TView View)>> ObserveReplace<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewReplace<T, TView>(source, cancellationToken);
}
public static Observable<CollectionMoveEvent<(T Value, TView View)>> ObserveMove<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewMove<T, TView>(source, cancellationToken);
}
public static Observable<CollectionResetEvent<T>> ObserveReset<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewReset<T, TView>(source, cancellationToken);
}
public static Observable<Unit> ObserveClear<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewClear<T, TView>(source, cancellationToken);
}
public static Observable<(int Index, int Count)> ObserveReverse<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewReverse<T, TView>(source, cancellationToken);
}
public static Observable<(int Index, int Count, IComparer<T>? Comparer)> ObserveSort<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
{
return new SynchronizedViewSort<T, TView>(source, cancellationToken);
}
public static Observable<int> ObserveCountChanged<T, TView>(this ISynchronizedView<T, TView> source, bool notifyCurrentCount = false, CancellationToken cancellationToken = default)
{
return new SynchronizedViewCountChanged<T, TView>(source, notifyCurrentCount, cancellationToken);
}
}
sealed class SynchronizedViewChanged<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<ViewChangedEvent<T, TView>>
{
protected override IDisposable SubscribeCore(Observer<ViewChangedEvent<T, TView>> observer)
{
return new _SynchronizedViewChanged(source, observer, cancellationToken);
}
sealed class _SynchronizedViewChanged(
ISynchronizedView<T, TView> source,
Observer<ViewChangedEvent<T, TView>> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, ViewChangedEvent<T, TView>>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.IsSingleItem)
{
var newArgs = new ViewChangedEvent<T, TView>(
eventArgs.Action,
eventArgs.NewItem,
eventArgs.OldItem,
eventArgs.NewStartingIndex,
eventArgs.OldStartingIndex,
eventArgs.SortOperation);
observer.OnNext(newArgs);
}
else
{
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
{
var index = eventArgs.NewStartingIndex;
for (int i = 0; i < eventArgs.NewValues.Length; i++)
{
var newItem = (eventArgs.NewValues[i], eventArgs.NewViews[i]);
var newArgs = new ViewChangedEvent<T, TView>(
eventArgs.Action,
newItem,
default,
index++,
eventArgs.OldStartingIndex,
eventArgs.SortOperation);
observer.OnNext(newArgs);
}
}
else if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
{
for (int i = 0; i < eventArgs.OldValues.Length; i++)
{
var oldItem = (eventArgs.OldValues[i], eventArgs.OldViews[i]);
var newArgs = new ViewChangedEvent<T, TView>(
eventArgs.Action,
default,
oldItem,
eventArgs.NewStartingIndex,
eventArgs.OldStartingIndex, // removed, uses same index
eventArgs.SortOperation);
observer.OnNext(newArgs);
}
}
}
}
}
}
sealed class SynchronizedViewAdd<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<CollectionAddEvent<(T, TView)>>
{
protected override IDisposable SubscribeCore(Observer<CollectionAddEvent<(T, TView)>> observer)
{
return new _SynchronizedViewAdd(source, observer, cancellationToken);
}
sealed class _SynchronizedViewAdd(
ISynchronizedView<T, TView> source,
Observer<CollectionAddEvent<(T, TView)>> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, CollectionAddEvent<(T, TView)>>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
{
if (eventArgs.IsSingleItem)
{
observer.OnNext(new CollectionAddEvent<(T, TView)>(eventArgs.NewStartingIndex, eventArgs.NewItem));
}
else
{
var index = eventArgs.NewStartingIndex;
for (int i = 0; i < eventArgs.NewValues.Length; i++)
{
observer.OnNext(new CollectionAddEvent<(T, TView)>(index++, (eventArgs.NewValues[i], eventArgs.NewViews[i])));
}
}
}
}
}
}
sealed class SynchronizedViewRemove<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<CollectionRemoveEvent<(T, TView)>>
{
protected override IDisposable SubscribeCore(Observer<CollectionRemoveEvent<(T, TView)>> observer)
{
return new _SynchronizedViewRemove(source, observer, cancellationToken);
}
sealed class _SynchronizedViewRemove(
ISynchronizedView<T, TView> source,
Observer<CollectionRemoveEvent<(T, TView)>> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, CollectionRemoveEvent<(T, TView)>>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
{
if (eventArgs.IsSingleItem)
{
observer.OnNext(new CollectionRemoveEvent<(T, TView)>(eventArgs.OldStartingIndex, eventArgs.OldItem));
}
else
{
for (int i = 0; i < eventArgs.OldValues.Length; i++)
{
observer.OnNext(new CollectionRemoveEvent<(T, TView)>(eventArgs.OldStartingIndex, (eventArgs.OldValues[i], eventArgs.OldViews[i])));
}
}
}
}
}
}
sealed class SynchronizedViewReplace<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<CollectionReplaceEvent<(T, TView)>>
{
protected override IDisposable SubscribeCore(Observer<CollectionReplaceEvent<(T, TView)>> observer)
{
return new _SynchronizedViewReplace(source, observer, cancellationToken);
}
sealed class _SynchronizedViewReplace(
ISynchronizedView<T, TView> source,
Observer<CollectionReplaceEvent<(T, TView)>> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, CollectionReplaceEvent<(T, TView)>>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Replace)
{
observer.OnNext(new CollectionReplaceEvent<(T, TView)>(eventArgs.NewStartingIndex, eventArgs.OldItem, eventArgs.NewItem));
}
}
}
}
sealed class SynchronizedViewMove<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<CollectionMoveEvent<(T, TView)>>
{
protected override IDisposable SubscribeCore(Observer<CollectionMoveEvent<(T, TView)>> observer)
{
return new _SynchronizedViewMove(source, observer, cancellationToken);
}
sealed class _SynchronizedViewMove(
ISynchronizedView<T, TView> source,
Observer<CollectionMoveEvent<(T, TView)>> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, CollectionMoveEvent<(T, TView)>>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Move)
{
observer.OnNext(new CollectionMoveEvent<(T, TView)>(eventArgs.OldStartingIndex, eventArgs.NewStartingIndex, eventArgs.NewItem));
}
}
}
}
sealed class SynchronizedViewReset<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<CollectionResetEvent<T>>
{
protected override IDisposable SubscribeCore(Observer<CollectionResetEvent<T>> observer)
{
return new _SynchronizedViewReset(source, observer, cancellationToken);
}
sealed class _SynchronizedViewReset(
ISynchronizedView<T, TView> source,
Observer<CollectionResetEvent<T>> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, CollectionResetEvent<T>>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Reset)
{
observer.OnNext(new CollectionResetEvent<T>(eventArgs.SortOperation));
}
}
}
}
sealed class SynchronizedViewClear<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<Unit>
{
protected override IDisposable SubscribeCore(Observer<Unit> observer)
{
return new _SynchronizedViewClear(source, observer, cancellationToken);
}
sealed class _SynchronizedViewClear(
ISynchronizedView<T, TView> source,
Observer<Unit> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, Unit>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsClear)
{
observer.OnNext(Unit.Default);
}
}
}
}
sealed class SynchronizedViewReverse<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<(int Index, int Count)>
{
protected override IDisposable SubscribeCore(Observer<(int Index, int Count)> observer)
{
return new _SynchronizedViewReverse(source, observer, cancellationToken);
}
sealed class _SynchronizedViewReverse(
ISynchronizedView<T, TView> source,
Observer<(int Index, int Count)> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, (int Index, int Count)>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsReverse)
{
observer.OnNext((eventArgs.SortOperation.Index, eventArgs.SortOperation.Count));
}
}
}
}
sealed class SynchronizedViewSort<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<(int Index, int Count, IComparer<T>? Comparer)>
{
protected override IDisposable SubscribeCore(Observer<(int Index, int Count, IComparer<T>? Comparer)> observer)
{
return new _SynchronizedViewSort(source, observer, cancellationToken);
}
sealed class _SynchronizedViewSort(
ISynchronizedView<T, TView> source,
Observer<(int Index, int Count, IComparer<T>? Comparer)> observer,
CancellationToken cancellationToken)
: SynchronizedViewObserverBase<T, TView, (int Index, int Count, IComparer<T>? Comparer)>(source, observer, cancellationToken)
{
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsSort)
{
observer.OnNext(eventArgs.SortOperation.AsTuple());
}
}
}
}
sealed class SynchronizedViewCountChanged<T, TView>(ISynchronizedView<T, TView> source, bool notifyCurrentCount, CancellationToken cancellationToken)
: Observable<int>
{
protected override IDisposable SubscribeCore(Observer<int> observer)
{
return new _SynchronizedViewCountChanged(source, notifyCurrentCount, observer, cancellationToken);
}
sealed class _SynchronizedViewCountChanged : SynchronizedViewObserverBase<T, TView, int>
{
int countPrev;
public _SynchronizedViewCountChanged(
ISynchronizedView<T, TView> source,
bool notifyCurrentCount,
Observer<int> observer,
CancellationToken cancellationToken) : base(source, observer, cancellationToken)
{
this.countPrev = source.Count;
if (notifyCurrentCount)
{
observer.OnNext(source.Count);
}
}
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
{
switch (eventArgs.Action)
{
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset when countPrev != source.Count:
observer.OnNext(source.Count);
break;
}
countPrev = source.Count;
}
}
}
sealed class SynchronizedViewRejected<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
: Observable<RejectedViewChangedEvent>
{
protected override IDisposable SubscribeCore(Observer<RejectedViewChangedEvent> observer)
{
return new _SynchronizedViewRejected(source, observer, cancellationToken);
}
sealed class _SynchronizedViewRejected : IDisposable
{
readonly ISynchronizedView<T, TView> source;
readonly Observer<RejectedViewChangedEvent> observer;
readonly CancellationTokenRegistration cancellationTokenRegistration;
readonly Action<RejectedViewChangedAction, int, int> handlerDelegate;
public _SynchronizedViewRejected(ISynchronizedView<T, TView> source, Observer<RejectedViewChangedEvent> observer, CancellationToken cancellationToken)
{
this.source = source;
this.observer = observer;
this.handlerDelegate = Handler;
source.RejectedViewChanged += handlerDelegate;
if (cancellationToken.CanBeCanceled)
{
cancellationTokenRegistration = cancellationToken.UnsafeRegister(static state =>
{
var s = (_SynchronizedViewRejected)state!;
s.observer.OnCompleted();
s.Dispose();
}, this);
}
}
public void Dispose()
{
source.RejectedViewChanged -= handlerDelegate;
cancellationTokenRegistration.Dispose();
}
void Handler(RejectedViewChangedAction rejectedViewChangedAction, int newIndex, int oldIndex)
{
observer.OnNext(new RejectedViewChangedEvent(rejectedViewChangedAction, newIndex, oldIndex));
}
}
}
abstract class SynchronizedViewObserverBase<T, TView, TEvent> : IDisposable
{
protected readonly ISynchronizedView<T, TView> source;
protected readonly Observer<TEvent> observer;
readonly CancellationTokenRegistration cancellationTokenRegistration;
readonly NotifyViewChangedEventHandler<T, TView> handlerDelegate;
public SynchronizedViewObserverBase(ISynchronizedView<T, TView> source, Observer<TEvent> observer, CancellationToken cancellationToken)
{
this.source = source;
this.observer = observer;
this.handlerDelegate = Handler;
source.ViewChanged += handlerDelegate;
if (cancellationToken.CanBeCanceled)
{
cancellationTokenRegistration = cancellationToken.UnsafeRegister(static state =>
{
var s = (SynchronizedViewObserverBase<T, TView, TEvent>)state!;
s.observer.OnCompleted();
s.Dispose();
}, this);
}
}
public void Dispose()
{
source.ViewChanged -= handlerDelegate;
cancellationTokenRegistration.Dispose();
}
protected abstract void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs);
}

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using R3; using R3;
@ -28,40 +27,15 @@ public readonly record struct CollectionResetEvent<T>
} }
} }
[StructLayout(LayoutKind.Auto)]
public readonly record struct CollectionChangedEvent<T>
{
public readonly NotifyCollectionChangedAction Action;
public readonly T NewItem;
public readonly T OldItem;
public readonly int NewStartingIndex;
public readonly int OldStartingIndex;
public readonly SortOperation<T> SortOperation;
public CollectionChangedEvent(NotifyCollectionChangedAction action, T newItem, T oldItem, int newStartingIndex, int oldStartingIndex, SortOperation<T> sortOperation)
{
Action = action;
NewItem = newItem;
OldItem = oldItem;
NewStartingIndex = newStartingIndex;
OldStartingIndex = oldStartingIndex;
SortOperation = sortOperation;
}
}
public readonly record struct DictionaryAddEvent<TKey, TValue>(TKey Key, TValue Value); public readonly record struct DictionaryAddEvent<TKey, TValue>(TKey Key, TValue Value);
public readonly record struct DictionaryRemoveEvent<TKey, TValue>(TKey Key, TValue Value); public readonly record struct DictionaryRemoveEvent<TKey, TValue>(TKey Key, TValue Value);
public readonly record struct DictionaryReplaceEvent<TKey, TValue>(TKey Key, TValue OldValue, TValue NewValue); public readonly record struct DictionaryReplaceEvent<TKey, TValue>(TKey Key, TValue OldValue, TValue NewValue);
public static partial class ObservableCollectionR3Extensions
{
public static Observable<CollectionChangedEvent<T>> ObserveChanged<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
{
return new ObservableCollectionChanged<T>(source, cancellationToken);
}
public static class ObservableCollectionR3Extensions
{
public static Observable<CollectionAddEvent<T>> ObserveAdd<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default) public static Observable<CollectionAddEvent<T>> ObserveAdd<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
{ {
return new ObservableCollectionAdd<T>(source, cancellationToken); return new ObservableCollectionAdd<T>(source, cancellationToken);
@ -128,74 +102,6 @@ public static class ObservableDictionaryR3Extensions
} }
} }
sealed class ObservableCollectionChanged<T>(IObservableCollection<T> collection, CancellationToken cancellationToken)
: Observable<CollectionChangedEvent<T>>
{
protected override IDisposable SubscribeCore(Observer<CollectionChangedEvent<T>> observer)
{
return new _ObservableCollectionAdd(collection, observer, cancellationToken);
}
sealed class _ObservableCollectionAdd(
IObservableCollection<T> collection,
Observer<CollectionChangedEvent<T>> observer,
CancellationToken cancellationToken)
: ObservableCollectionObserverBase<T, CollectionChangedEvent<T>>(collection, observer, cancellationToken)
{
protected override void Handler(in NotifyCollectionChangedEventArgs<T> eventArgs)
{
if (eventArgs.IsSingleItem)
{
var newArgs = new CollectionChangedEvent<T>(
eventArgs.Action,
eventArgs.NewItem,
eventArgs.OldItem,
eventArgs.NewStartingIndex,
eventArgs.OldStartingIndex,
eventArgs.SortOperation);
observer.OnNext(newArgs);
}
else
{
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
{
var i = eventArgs.NewStartingIndex;
foreach (var item in eventArgs.NewItems)
{
var newArgs = new CollectionChangedEvent<T>(
eventArgs.Action,
item,
eventArgs.OldItem,
newStartingIndex: i,
eventArgs.OldStartingIndex,
eventArgs.SortOperation);
if (eventArgs.NewStartingIndex != -1) i++;
observer.OnNext(newArgs);
}
}
else if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in eventArgs.OldItems)
{
var newArgs = new CollectionChangedEvent<T>(
eventArgs.Action,
eventArgs.NewItem,
item,
eventArgs.NewStartingIndex,
eventArgs.OldStartingIndex, // removed, uses same index
eventArgs.SortOperation);
observer.OnNext(newArgs);
}
}
}
}
}
}
sealed class ObservableCollectionAdd<T>(IObservableCollection<T> collection, CancellationToken cancellationToken) sealed class ObservableCollectionAdd<T>(IObservableCollection<T> collection, CancellationToken cancellationToken)
: Observable<CollectionAddEvent<T>> : Observable<CollectionAddEvent<T>>
{ {
@ -255,9 +161,10 @@ sealed class ObservableCollectionRemove<T>(IObservableCollection<T> collection,
} }
else else
{ {
var i = eventArgs.OldStartingIndex;
foreach (var item in eventArgs.OldItems) foreach (var item in eventArgs.OldItems)
{ {
observer.OnNext(new CollectionRemoveEvent<T>(eventArgs.OldStartingIndex, item)); // remove uses same index observer.OnNext(new CollectionRemoveEvent<T>(i++, item));
} }
} }
} }

View File

@ -35,16 +35,7 @@ namespace ObservableCollections
public void Post(CollectionEventDispatcherEventArgs ev) public void Post(CollectionEventDispatcherEventArgs ev)
{ {
if (SynchronizationContext.Current == null) synchronizationContext.Post(callback, ev);
{
// non-UI thread, post the event asynchronously
synchronizationContext.Post(callback, ev);
}
else
{
// UI thread, send the event synchronously
synchronizationContext.Send(callback, ev);
}
} }
static void SendOrPostCallback(object? state) static void SendOrPostCallback(object? state)

View File

@ -35,7 +35,7 @@ namespace ObservableCollections
public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisposable public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisposable
{ {
object SyncRoot { get; } object SyncRoot { get; }
ISynchronizedViewFilter<T, TView> Filter { get; } ISynchronizedViewFilter<T> Filter { get; }
IEnumerable<(T Value, TView View)> Filtered { get; } IEnumerable<(T Value, TView View)> Filtered { get; }
IEnumerable<(T Value, TView View)> Unfiltered { get; } IEnumerable<(T Value, TView View)> Unfiltered { get; }
int UnfilteredCount { get; } int UnfilteredCount { get; }
@ -44,11 +44,11 @@ namespace ObservableCollections
event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
event Action<NotifyCollectionChangedAction>? CollectionStateChanged; event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
void AttachFilter(ISynchronizedViewFilter<T, TView> filter); void AttachFilter(ISynchronizedViewFilter<T> filter);
void ResetFilter(); void ResetFilter();
ISynchronizedViewList<TView> ToViewList(); ISynchronizedViewList<TView> ToViewList();
NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(); INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
} }
public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView> public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
@ -57,15 +57,10 @@ namespace ObservableCollections
void SetViewAt(int index, TView view); void SetViewAt(int index, TView view);
void SetToSourceCollection(int index, T value); void SetToSourceCollection(int index, T value);
void AddToSourceCollection(T value); void AddToSourceCollection(T value);
void InsertIntoSourceCollection(int index, T value);
bool RemoveFromSourceCollection(T value);
void RemoveAtSourceCollection(int index);
void ClearSourceCollection();
IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter); IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter);
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(); INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter); INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
} }
public interface ISynchronizedViewList<out TView> : IReadOnlyList<TView>, IDisposable public interface ISynchronizedViewList<out TView> : IReadOnlyList<TView>, IDisposable
@ -77,128 +72,10 @@ namespace ObservableCollections
new TView this[int index] { get; set; } new TView this[int index] { get; set; }
} }
// only for compatibility, use NotifyCollectionChangedSynchronizedViewList insetad.
// [Obsolete] in future
public interface INotifyCollectionChangedSynchronizedViewList<TView> : IList<TView>, IList, ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged public interface INotifyCollectionChangedSynchronizedViewList<TView> : IList<TView>, IList, ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
{ {
} }
// IColleciton<T>.Count and ICollection.Count will be ambigious so use abstract class instead of interface
public abstract class NotifyCollectionChangedSynchronizedViewList<TView> :
INotifyCollectionChangedSynchronizedViewList<TView>,
IWritableSynchronizedViewList<TView>,
IList<TView>,
IList
{
protected readonly object gate = new object();
public abstract TView this[int index] { get; set; }
object? IList.this[int index]
{
get
{
return this[index];
}
set => ((IList<TView>)this)[index] = (TView)value!;
}
public abstract int Count { get; }
public virtual bool IsReadOnly { get; } = true;
public bool IsFixedSize => IsReadOnly;
public bool IsSynchronized => true;
public object SyncRoot => gate;
public abstract event NotifyCollectionChangedEventHandler? CollectionChanged;
public abstract event PropertyChangedEventHandler? PropertyChanged;
public abstract void Add(TView item);
int IList.Add(object? value)
{
Add((TView)value!);
return Count - 1;
}
public abstract void Insert(int index, TView item);
public abstract bool Remove(TView item);
public abstract void RemoveAt(int index);
public abstract void Clear();
public abstract bool Contains(TView item);
bool IList.Contains(object? value)
{
if (IsCompatibleObject(value))
{
return Contains((TView)value!);
}
return false;
}
public abstract void Dispose();
public abstract IEnumerator<TView> GetEnumerator();
public abstract int IndexOf(TView item);
int IList.IndexOf(object? item)
{
if (IsCompatibleObject(item))
{
return IndexOf((TView)item!);
}
return -1;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
static bool IsCompatibleObject(object? value)
{
return value is TView || value == null && default(TView) == null;
}
void ICollection<TView>.Clear()
{
Clear();
}
void IList.Clear()
{
Clear();
}
void ICollection<TView>.CopyTo(TView[] array, int arrayIndex) => throw new NotSupportedException();
void ICollection.CopyTo(Array array, int index) => throw new NotSupportedException();
void IList<TView>.Insert(int index, TView item)
{
Insert(index, item);
}
void IList.Insert(int index, object? value)
{
Insert(index, (TView)value!);
}
bool ICollection<TView>.Remove(TView item)
{
return Remove(item!);
}
void IList.Remove(object? value)
{
Remove((TView)value!);
}
void IList.RemoveAt(int index)
{
RemoveAt(index);
}
void IList<TView>.RemoveAt(int index) => throw new NotSupportedException();
}
public static class ObservableCollectionExtensions public static class ObservableCollectionExtensions
{ {
public static ISynchronizedViewList<T> ToViewList<T>(this IObservableCollection<T> collection) public static ISynchronizedViewList<T> ToViewList<T>(this IObservableCollection<T> collection)
@ -209,28 +86,28 @@ namespace ObservableCollections
public static ISynchronizedViewList<TView> ToViewList<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform) public static ISynchronizedViewList<TView> ToViewList<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform)
{ {
// Optimized for non filtered // Optimized for non filtered
return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform), isSupportRangeFeature: true, null, null); return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform));
} }
public static NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged<T>(this IObservableCollection<T> collection) public static INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged<T>(this IObservableCollection<T> collection)
{ {
return ToNotifyCollectionChanged(collection, null); return ToNotifyCollectionChanged(collection, null);
} }
public static NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged<T>(this IObservableCollection<T> collection, ICollectionEventDispatcher? collectionEventDispatcher) public static INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged<T>(this IObservableCollection<T> collection, ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return ToNotifyCollectionChanged(collection, static x => x, collectionEventDispatcher); return ToNotifyCollectionChanged(collection, static x => x, collectionEventDispatcher);
} }
public static NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform) public static INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform)
{ {
return ToNotifyCollectionChanged(collection, transform, null!); return ToNotifyCollectionChanged(collection, transform, null!);
} }
public static NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher) public static INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher)
{ {
// Optimized for non filtered // Optimized for non filtered
return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform), isSupportRangeFeature: false, collectionEventDispatcher, null); return new NonFilteredNotifyCollectionChangedSynchronizedViewList<T, TView>(collection.CreateView(transform), collectionEventDispatcher);
} }
} }
} }

View File

@ -1,38 +1,24 @@
using System; using System;
using System.Collections.Specialized;
namespace ObservableCollections namespace ObservableCollections
{ {
// Obsolete...
[Obsolete("this interface is obsoleted. Use ISynchronizedViewFilter<T, TView> instead.")]
public interface ISynchronizedViewFilter<T> public interface ISynchronizedViewFilter<T>
{ {
bool IsMatch(T value); bool IsMatch(T value);
} }
public interface ISynchronizedViewFilter<T, TView> public class SynchronizedViewFilter<T>(Func<T, bool> isMatch) : ISynchronizedViewFilter<T>
{ {
bool IsMatch(T value, TView view); public static readonly ISynchronizedViewFilter<T> Null = new NullViewFilter();
}
internal class SynchronizedViewValueOnlyFilter<T, TView>(Func<T, bool> isMatch) : ISynchronizedViewFilter<T, TView> public bool IsMatch(T value) => isMatch(value);
{
public bool IsMatch(T value, TView view) => isMatch(value);
class NullViewFilter : ISynchronizedViewFilter<T, TView> class NullViewFilter : ISynchronizedViewFilter<T>
{ {
public bool IsMatch(T value, TView view) => true; public bool IsMatch(T value) => true;
} }
} }
public class SynchronizedViewFilter<T, TView>(Func<T, TView, bool> isMatch) : ISynchronizedViewFilter<T, TView>
{
public static readonly ISynchronizedViewFilter<T, TView> Null = new NullViewFilter();
public bool IsMatch(T value, TView view) => isMatch(value, view);
class NullViewFilter : ISynchronizedViewFilter<T, TView>
{
public bool IsMatch(T value, TView view) => true;
}
}
} }

View File

@ -7,7 +7,7 @@ using System.Linq;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableDictionary<TKey, TValue> public sealed partial class ObservableDictionary<TKey, TValue>
{ {
public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform) public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform)
{ {
@ -19,7 +19,7 @@ namespace ObservableCollections
{ {
readonly ObservableDictionary<TKey, TValue> source; readonly ObservableDictionary<TKey, TValue> source;
readonly Func<KeyValuePair<TKey, TValue>, TView> selector; readonly Func<KeyValuePair<TKey, TValue>, TView> selector;
ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter; ISynchronizedViewFilter<KeyValuePair<TKey, TValue>> filter;
readonly Dictionary<TKey, (TValue, TView)> dict; readonly Dictionary<TKey, (TValue, TView)> dict;
int filteredCount; int filteredCount;
@ -27,7 +27,7 @@ namespace ObservableCollections
{ {
this.source = source; this.source = source;
this.selector = selector; this.selector = selector;
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null; this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>>.Null;
this.SyncRoot = new object(); this.SyncRoot = new object();
lock (source.SyncRoot) lock (source.SyncRoot)
{ {
@ -42,7 +42,7 @@ namespace ObservableCollections
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged; public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
public ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> Filter public ISynchronizedViewFilter<KeyValuePair<TKey, TValue>> Filter
{ {
get { lock (SyncRoot) return filter; } get { lock (SyncRoot) return filter; }
} }
@ -74,7 +74,7 @@ namespace ObservableCollections
this.source.CollectionChanged -= SourceCollectionChanged; this.source.CollectionChanged -= SourceCollectionChanged;
} }
public void AttachFilter(ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter) public void AttachFilter(ISynchronizedViewFilter<KeyValuePair<TKey, TValue>> filter)
{ {
if (filter.IsNullFilter()) if (filter.IsNullFilter())
{ {
@ -89,7 +89,7 @@ namespace ObservableCollections
foreach (var v in dict) foreach (var v in dict)
{ {
var value = new KeyValuePair<TKey, TValue>(v.Key, v.Value.Item1); var value = new KeyValuePair<TKey, TValue>(v.Key, v.Value.Item1);
if (filter.IsMatch(value, v.Value.Item2)) if (filter.IsMatch(value))
{ {
filteredCount++; filteredCount++;
} }
@ -103,7 +103,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null; this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>>.Null;
this.filteredCount = dict.Count; this.filteredCount = dict.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<KeyValuePair<TKey, TValue>, TView>(NotifyCollectionChangedAction.Reset, true)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<KeyValuePair<TKey, TValue>, TView>(NotifyCollectionChangedAction.Reset, true));
} }
@ -111,17 +111,17 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, isSupportRangeFeature: true); return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
{ {
return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, isSupportRangeFeature: false); return new NotifyCollectionChangedSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, null);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher); return new NotifyCollectionChangedSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, collectionEventDispatcher);
} }
public IEnumerator<TView> GetEnumerator() public IEnumerator<TView> GetEnumerator()
@ -131,7 +131,7 @@ namespace ObservableCollections
foreach (var item in dict) foreach (var item in dict)
{ {
var v = (new KeyValuePair<TKey, TValue>(item.Key, item.Value.Item1), item.Value.Item2); var v = (new KeyValuePair<TKey, TValue>(item.Key, item.Value.Item1), item.Value.Item2);
if (filter.IsMatch(v)) if (filter.IsMatch(v.Item1))
{ {
yield return v.Item2; yield return v.Item2;
} }
@ -150,7 +150,7 @@ namespace ObservableCollections
foreach (var item in dict) foreach (var item in dict)
{ {
var v = (new KeyValuePair<TKey, TValue>(item.Key, item.Value.Item1), item.Value.Item2); var v = (new KeyValuePair<TKey, TValue>(item.Key, item.Value.Item1), item.Value.Item2);
if (filter.IsMatch(v)) if (filter.IsMatch(v.Item1))
{ {
yield return v; yield return v;
} }

View File

@ -7,7 +7,7 @@ using System.Linq;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyObservableDictionary<TKey, TValue> public sealed partial class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyObservableDictionary<TKey, TValue>
where TKey : notnull where TKey : notnull
{ {
readonly Dictionary<TKey, TValue> dictionary; readonly Dictionary<TKey, TValue> dictionary;

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableFixedSizeRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T> public sealed partial class ObservableFixedSizeRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
{ {
readonly RingBuffer<T> buffer; readonly RingBuffer<T> buffer;
readonly int capacity; readonly int capacity;

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableHashSet<T> : IReadOnlyCollection<T>, IObservableCollection<T> public sealed partial class ObservableHashSet<T> : IReadOnlyCollection<T>, IObservableCollection<T>
{ {
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform) public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
{ {
@ -17,7 +17,7 @@ namespace ObservableCollections
sealed class View<TView> : ISynchronizedView<T, TView> sealed class View<TView> : ISynchronizedView<T, TView>
{ {
public ISynchronizedViewFilter<T, TView> Filter public ISynchronizedViewFilter<T> Filter
{ {
get { lock (SyncRoot) return filter; } get { lock (SyncRoot) return filter; }
} }
@ -27,7 +27,7 @@ namespace ObservableCollections
readonly Dictionary<T, (T, TView)> dict; readonly Dictionary<T, (T, TView)> dict;
int filteredCount; int filteredCount;
ISynchronizedViewFilter<T, TView> filter; ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged; public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
@ -39,7 +39,7 @@ namespace ObservableCollections
{ {
this.source = source; this.source = source;
this.selector = selector; this.selector = selector;
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.SyncRoot = new object(); this.SyncRoot = new object();
lock (source.SyncRoot) lock (source.SyncRoot)
{ {
@ -71,7 +71,7 @@ namespace ObservableCollections
} }
} }
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter) public void AttachFilter(ISynchronizedViewFilter<T> filter)
{ {
if (filter.IsNullFilter()) if (filter.IsNullFilter())
{ {
@ -85,7 +85,7 @@ namespace ObservableCollections
this.filteredCount = 0; this.filteredCount = 0;
foreach (var (_, (value, view)) in dict) foreach (var (_, (value, view)) in dict)
{ {
if (filter.IsMatch(value, view)) if (filter.IsMatch(value))
{ {
filteredCount++; filteredCount++;
} }
@ -98,7 +98,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = dict.Count; this.filteredCount = dict.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
@ -106,17 +106,17 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
} }
public IEnumerator<TView> GetEnumerator() public IEnumerator<TView> GetEnumerator()
@ -125,7 +125,7 @@ namespace ObservableCollections
{ {
foreach (var item in dict) foreach (var item in dict)
{ {
if (filter.IsMatch(item.Value)) if (filter.IsMatch(item.Value.Item1))
{ {
yield return item.Value.Item2; yield return item.Value.Item2;
} }
@ -143,7 +143,7 @@ namespace ObservableCollections
{ {
foreach (var item in dict) foreach (var item in dict)
{ {
if (filter.IsMatch(item.Value)) if (filter.IsMatch(item.Value.Item1))
{ {
yield return item.Value; yield return item.Value;
} }

View File

@ -8,7 +8,7 @@ using System.Linq;
namespace ObservableCollections namespace ObservableCollections
{ {
// can not implements ISet<T> because set operation can not get added/removed values. // can not implements ISet<T> because set operation can not get added/removed values.
public partial class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T> public sealed partial class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T>
where T : notnull where T : notnull
{ {
readonly HashSet<T> set; readonly HashSet<T> set;

View File

@ -7,7 +7,7 @@ using System.Text;
namespace ObservableCollections; namespace ObservableCollections;
public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T> public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
{ {
// override extension methods(IObservableCollection.cs ObservableCollectionExtensions) // override extension methods(IObservableCollection.cs ObservableCollectionExtensions)
@ -24,7 +24,7 @@ public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
/// <summary> /// <summary>
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range. /// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
/// </summary> /// </summary>
public NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim() public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim()
{ {
return new ObservableListSynchronizedViewList<T>(this, null); return new ObservableListSynchronizedViewList<T>(this, null);
} }
@ -32,7 +32,7 @@ public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
/// <summary> /// <summary>
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range. /// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
/// </summary> /// </summary>
public NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new ObservableListSynchronizedViewList<T>(this, collectionEventDispatcher); return new ObservableListSynchronizedViewList<T>(this, collectionEventDispatcher);
} }
@ -48,7 +48,7 @@ public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
//} //}
} }
internal sealed class ObservableListSynchronizedViewList<T> : NotifyCollectionChangedSynchronizedViewList<T> internal sealed class ObservableListSynchronizedViewList<T> : INotifyCollectionChangedSynchronizedViewList<T>, IList<T>, IList
{ {
static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count");
static readonly Action<NotifyCollectionChangedEventArgs> raiseChangedEventInvoke = RaiseChangedEvent; static readonly Action<NotifyCollectionChangedEventArgs> raiseChangedEventInvoke = RaiseChangedEvent;
@ -56,8 +56,8 @@ internal sealed class ObservableListSynchronizedViewList<T> : NotifyCollectionCh
readonly ObservableList<T> parent; readonly ObservableList<T> parent;
readonly ICollectionEventDispatcher eventDispatcher; readonly ICollectionEventDispatcher eventDispatcher;
public override event NotifyCollectionChangedEventHandler? CollectionChanged; public event NotifyCollectionChangedEventHandler? CollectionChanged;
public override event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
public ObservableListSynchronizedViewList(ObservableList<T> parent, ICollectionEventDispatcher? eventDispatcher) public ObservableListSynchronizedViewList(ObservableList<T> parent, ICollectionEventDispatcher? eventDispatcher)
{ {
@ -161,61 +161,130 @@ internal sealed class ObservableListSynchronizedViewList<T> : NotifyCollectionCh
} }
} }
public override T this[int index] public T this[int index] => parent[index];
{
get
{
return parent[index];
}
set
{
parent[index] = value;
}
}
public override int Count => parent.Count; public int Count => parent.Count;
public override IEnumerator<T> GetEnumerator() public IEnumerator<T> GetEnumerator()
{ {
return parent.GetEnumerator(); return parent.GetEnumerator();
} }
public override void Dispose() IEnumerator IEnumerable.GetEnumerator()
{
return parent.GetEnumerator();
}
public void Dispose()
{ {
parent.CollectionChanged -= Parent_CollectionChanged; parent.CollectionChanged -= Parent_CollectionChanged;
} }
public override void Add(T item) // IList<T>, IList implementation
T IList<T>.this[int index]
{ {
parent.Add(item); get => ((IReadOnlyList<T>)this)[index];
} set => throw new NotSupportedException();
public override void Insert(int index, T item)
{
parent.Insert(index, item);
} }
public override bool Remove(T item) object? IList.this[int index]
{ {
return parent.Remove(item); get
{
return this[index];
}
set => throw new NotSupportedException();
} }
public override void RemoveAt(int index) static bool IsCompatibleObject(object? value)
{ {
parent.RemoveAt(index); return value is T || value == null && default(T) == null;
} }
public override void Clear() public bool IsReadOnly => true;
public bool IsFixedSize => false;
public bool IsSynchronized => true;
public object SyncRoot => parent.SyncRoot;
public void Add(T item)
{ {
parent.Clear(); throw new NotSupportedException();
} }
public override bool Contains(T item) public int Add(object? value)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Contains(T item)
{ {
return parent.Contains(item); return parent.Contains(item);
} }
public override int IndexOf(T item) public bool Contains(object? value)
{
if (IsCompatibleObject(value))
{
return Contains((T)value!);
}
return false;
}
public void CopyTo(T[] array, int arrayIndex)
{
throw new NotSupportedException();
}
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
public int IndexOf(T item)
{ {
return parent.IndexOf(item); return parent.IndexOf(item);
} }
}
public int IndexOf(object? item)
{
if (IsCompatibleObject(item))
{
return IndexOf((T)item!);
}
return -1;
}
public void Insert(int index, T item)
{
throw new NotSupportedException();
}
public void Insert(int index, object? value)
{
throw new NotImplementedException();
}
public bool Remove(T item)
{
throw new NotSupportedException();
}
public void Remove(object? value)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotSupportedException();
}
}

View File

@ -7,7 +7,7 @@ using System.Linq;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T> public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
{ {
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform) public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
{ {
@ -19,12 +19,12 @@ namespace ObservableCollections
return new View<TView>(this, transform); return new View<TView>(this, transform);
} }
public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged()
{ {
return ToWritableNotifyCollectionChanged(null); return ToWritableNotifyCollectionChanged(null);
} }
public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return ToWritableNotifyCollectionChanged( return ToWritableNotifyCollectionChanged(
static x => x, static x => x,
@ -36,19 +36,19 @@ namespace ObservableCollections
collectionEventDispatcher); collectionEventDispatcher);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter) public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter)
{ {
return ToWritableNotifyCollectionChanged(transform, converter, null!); return ToWritableNotifyCollectionChanged(transform, converter, null!);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter, ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter, ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new NonFilteredSynchronizedViewList<T, TView>(CreateView(transform), isSupportRangeFeature: false, collectionEventDispatcher, converter); return new NonFilteredNotifyCollectionChangedSynchronizedViewList<T, TView>(CreateView(transform), collectionEventDispatcher, converter);
} }
internal sealed class View<TView> : ISynchronizedView<T, TView>, IWritableSynchronizedView<T, TView> internal sealed class View<TView> : ISynchronizedView<T, TView>, IWritableSynchronizedView<T, TView>
{ {
public ISynchronizedViewFilter<T, TView> Filter public ISynchronizedViewFilter<T> Filter
{ {
get get
{ {
@ -61,7 +61,7 @@ namespace ObservableCollections
internal readonly List<(T, TView)> list; // unsafe, be careful to use internal readonly List<(T, TView)> list; // unsafe, be careful to use
int filteredCount; int filteredCount;
ISynchronizedViewFilter<T, TView> filter; ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged; public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
@ -73,7 +73,7 @@ namespace ObservableCollections
{ {
this.source = source; this.source = source;
this.selector = selector; this.selector = selector;
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.SyncRoot = new object(); this.SyncRoot = new object();
lock (source.SyncRoot) lock (source.SyncRoot)
{ {
@ -105,7 +105,7 @@ namespace ObservableCollections
} }
} }
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter) public void AttachFilter(ISynchronizedViewFilter<T> filter)
{ {
if (filter.IsNullFilter()) if (filter.IsNullFilter())
{ {
@ -119,7 +119,7 @@ namespace ObservableCollections
this.filteredCount = 0; this.filteredCount = 0;
for (var i = 0; i < list.Count; i++) for (var i = 0; i < list.Count; i++)
{ {
if (filter.IsMatch(list[i])) if (filter.IsMatch(list[i].Item1))
{ {
filteredCount++; filteredCount++;
} }
@ -133,7 +133,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = list.Count; this.filteredCount = list.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
@ -141,17 +141,17 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
} }
public IEnumerator<TView> GetEnumerator() public IEnumerator<TView> GetEnumerator()
@ -160,7 +160,7 @@ namespace ObservableCollections
{ {
foreach (var item in list) foreach (var item in list)
{ {
if (filter.IsMatch(item)) if (filter.IsMatch(item.Item1))
{ {
yield return item.Item2; yield return item.Item2;
} }
@ -178,7 +178,7 @@ namespace ObservableCollections
{ {
foreach (var item in list) foreach (var item in list)
{ {
if (filter.IsMatch(item)) if (filter.IsMatch(item.Item1))
{ {
yield return item; yield return item;
} }
@ -235,7 +235,7 @@ namespace ObservableCollections
var view = selector(item); var view = selector(item);
views.Span[i] = view; views.Span[i] = view;
valueViews.Span[i] = (item, view); valueViews.Span[i] = (item, view);
var isMatch = matches.Span[i] = Filter.IsMatch(item, view); var isMatch = matches.Span[i] = Filter.IsMatch(item);
if (isMatch) if (isMatch)
{ {
filteredCount++; // increment in this process filteredCount++; // increment in this process
@ -271,7 +271,7 @@ namespace ObservableCollections
var item = list[i]; var item = list[i];
values.Span[j] = item.Item1; values.Span[j] = item.Item1;
views.Span[j] = item.Item2; views.Span[j] = item.Item2;
var isMatch = matches.Span[j] = Filter.IsMatch(item); var isMatch = matches.Span[j] = Filter.IsMatch(item.Item1);
if (isMatch) if (isMatch)
{ {
filteredCount--; // decrement in this process filteredCount--; // decrement in this process
@ -367,74 +367,30 @@ namespace ObservableCollections
source.Add(value); source.Add(value);
} }
} }
public void InsertIntoSourceCollection(int index, T value)
{
lock (SyncRoot)
{
source.Insert(index, value);
}
}
public bool RemoveFromSourceCollection(T value)
{
lock (SyncRoot)
{
return source.Remove(value);
}
}
public void RemoveAtSourceCollection(int index)
{
lock (SyncRoot)
{
source.RemoveAt(index);
}
}
public void ClearSourceCollection()
{
lock (SyncRoot)
{
source.Clear();
}
}
public IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter) public IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true, converter: converter); return new FiltableWritableSynchronizedViewList<T, TView>(this, converter);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null, converter);
isSupportRangeFeature: false,
converter: static (TView newView, T originalValue, ref bool setValue) =>
{
setValue = true;
return originalValue;
});
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter) public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, converter: converter); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher,
static (TView newView, T originalValue, ref bool setValue) =>
{
setValue = true;
return originalValue;
});
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher, converter);
isSupportRangeFeature: false,
eventDispatcher: collectionEventDispatcher,
converter: static (TView newView, T originalValue, ref bool setValue) =>
{
setValue = true;
return originalValue;
});
}
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher)
{
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher, converter);
} }
#endregion #endregion

View File

@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T> public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
{ {
readonly List<T> list; readonly List<T> list;
public object SyncRoot { get; } = new(); public object SyncRoot { get; } = new();

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T> public sealed partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
{ {
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform) public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
{ {
@ -22,7 +22,7 @@ namespace ObservableCollections
protected readonly Queue<(T, TView)> queue; protected readonly Queue<(T, TView)> queue;
int filteredCount; int filteredCount;
ISynchronizedViewFilter<T, TView> filter; ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged; public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
@ -30,7 +30,7 @@ namespace ObservableCollections
public object SyncRoot { get; } public object SyncRoot { get; }
public ISynchronizedViewFilter<T, TView> Filter public ISynchronizedViewFilter<T> Filter
{ {
get { lock (SyncRoot) return filter; } get { lock (SyncRoot) return filter; }
} }
@ -39,7 +39,7 @@ namespace ObservableCollections
{ {
this.source = source; this.source = source;
this.selector = selector; this.selector = selector;
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.SyncRoot = new object(); this.SyncRoot = new object();
lock (source.SyncRoot) lock (source.SyncRoot)
{ {
@ -71,7 +71,7 @@ namespace ObservableCollections
} }
} }
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter) public void AttachFilter(ISynchronizedViewFilter<T> filter)
{ {
if (filter.IsNullFilter()) if (filter.IsNullFilter())
{ {
@ -85,7 +85,7 @@ namespace ObservableCollections
this.filteredCount = 0; this.filteredCount = 0;
foreach (var (value, view) in queue) foreach (var (value, view) in queue)
{ {
if (filter.IsMatch(value, view)) if (filter.IsMatch(value))
{ {
filteredCount++; filteredCount++;
} }
@ -98,7 +98,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = queue.Count; this.filteredCount = queue.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
@ -106,17 +106,17 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
} }
public IEnumerator<TView> GetEnumerator() public IEnumerator<TView> GetEnumerator()
@ -125,7 +125,7 @@ namespace ObservableCollections
{ {
foreach (var item in queue) foreach (var item in queue)
{ {
if (filter.IsMatch(item)) if (filter.IsMatch(item.Item1))
{ {
yield return item.Item2; yield return item.Item2;
} }
@ -143,7 +143,7 @@ namespace ObservableCollections
{ {
foreach (var item in queue) foreach (var item in queue)
{ {
if (filter.IsMatch(item)) if (filter.IsMatch(item.Item1))
{ {
yield return item; yield return item;
} }

View File

@ -10,7 +10,7 @@ using System.Linq;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T> public sealed partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
{ {
readonly Queue<T> queue; readonly Queue<T> queue;
public object SyncRoot { get; } = new object(); public object SyncRoot { get; } = new object();

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableRingBuffer<T> public sealed partial class ObservableRingBuffer<T>
{ {
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform) public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
{ {
@ -18,7 +18,7 @@ namespace ObservableCollections
// used with ObservableFixedSizeRingBuffer // used with ObservableFixedSizeRingBuffer
internal sealed class View<TView> : ISynchronizedView<T, TView> internal sealed class View<TView> : ISynchronizedView<T, TView>
{ {
public ISynchronizedViewFilter<T, TView> Filter public ISynchronizedViewFilter<T> Filter
{ {
get { lock (SyncRoot) return filter; } get { lock (SyncRoot) return filter; }
} }
@ -28,7 +28,7 @@ namespace ObservableCollections
readonly RingBuffer<(T, TView)> ringBuffer; readonly RingBuffer<(T, TView)> ringBuffer;
int filteredCount; int filteredCount;
ISynchronizedViewFilter<T, TView> filter; ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged; public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
@ -40,7 +40,7 @@ namespace ObservableCollections
{ {
this.source = source; this.source = source;
this.selector = selector; this.selector = selector;
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.SyncRoot = new object(); this.SyncRoot = new object();
lock (source.SyncRoot) lock (source.SyncRoot)
{ {
@ -72,7 +72,7 @@ namespace ObservableCollections
} }
} }
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter) public void AttachFilter(ISynchronizedViewFilter<T> filter)
{ {
if (filter.IsNullFilter()) if (filter.IsNullFilter())
{ {
@ -87,7 +87,7 @@ namespace ObservableCollections
for (var i = 0; i < ringBuffer.Count; i++) for (var i = 0; i < ringBuffer.Count; i++)
{ {
var (value, view) = ringBuffer[i]; var (value, view) = ringBuffer[i];
if (filter.IsMatch(value, view)) if (filter.IsMatch(value))
{ {
filteredCount++; filteredCount++;
} }
@ -100,7 +100,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = ringBuffer.Count; this.filteredCount = ringBuffer.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
@ -108,22 +108,22 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
} }
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
} }
} }
@ -133,7 +133,7 @@ namespace ObservableCollections
{ {
foreach (var item in ringBuffer) foreach (var item in ringBuffer)
{ {
if (filter.IsMatch(item)) if (filter.IsMatch(item.Item1))
{ {
yield return item.Item2; yield return item.Item2;
} }
@ -151,7 +151,7 @@ namespace ObservableCollections
{ {
foreach (var item in ringBuffer) foreach (var item in ringBuffer)
{ {
if (filter.IsMatch(item)) if (filter.IsMatch(item.Item1))
{ {
yield return item; yield return item;
} }

View File

@ -6,7 +6,7 @@ using System.Collections;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T> public sealed partial class ObservableRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
{ {
readonly RingBuffer<T> buffer; readonly RingBuffer<T> buffer;

View File

@ -7,7 +7,7 @@ using System.Linq;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T> public sealed partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
{ {
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform) public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
{ {
@ -21,7 +21,7 @@ namespace ObservableCollections
protected readonly Stack<(T, TView)> stack; protected readonly Stack<(T, TView)> stack;
int filteredCount; int filteredCount;
ISynchronizedViewFilter<T, TView> filter; ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged; public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
@ -29,7 +29,7 @@ namespace ObservableCollections
public object SyncRoot { get; } public object SyncRoot { get; }
public ISynchronizedViewFilter<T, TView> Filter public ISynchronizedViewFilter<T> Filter
{ {
get { lock (SyncRoot) return filter; } get { lock (SyncRoot) return filter; }
} }
@ -38,7 +38,7 @@ namespace ObservableCollections
{ {
this.source = source; this.source = source;
this.selector = selector; this.selector = selector;
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.SyncRoot = new object(); this.SyncRoot = new object();
lock (source.SyncRoot) lock (source.SyncRoot)
{ {
@ -70,7 +70,7 @@ namespace ObservableCollections
} }
} }
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter) public void AttachFilter(ISynchronizedViewFilter<T> filter)
{ {
if (filter.IsNullFilter()) if (filter.IsNullFilter())
{ {
@ -84,7 +84,7 @@ namespace ObservableCollections
this.filteredCount = 0; this.filteredCount = 0;
foreach (var (value, view) in stack) foreach (var (value, view) in stack)
{ {
if (filter.IsMatch(value, view)) if (filter.IsMatch(value))
{ {
filteredCount++; filteredCount++;
} }
@ -97,7 +97,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
this.filter = SynchronizedViewFilter<T, TView>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = stack.Count; this.filteredCount = stack.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
@ -105,22 +105,22 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
} }
} }
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher); return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
} }
} }
@ -130,7 +130,7 @@ namespace ObservableCollections
{ {
foreach (var item in stack) foreach (var item in stack)
{ {
if (filter.IsMatch(item)) if (filter.IsMatch(item.Item1))
{ {
yield return item.Item2; yield return item.Item2;
} }
@ -148,7 +148,7 @@ namespace ObservableCollections
{ {
foreach (var item in stack) foreach (var item in stack)
{ {
if (filter.IsMatch(item)) if (filter.IsMatch(item.Item1))
{ {
yield return item; yield return item;
} }

View File

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
namespace ObservableCollections namespace ObservableCollections
{ {
public partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T> public sealed partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
{ {
readonly Stack<T> stack; readonly Stack<T> stack;
public object SyncRoot { get; } = new object(); public object SyncRoot { get; } = new object();
@ -166,7 +166,7 @@ namespace ObservableCollections
} }
} }
public bool TryPeek([MaybeNullWhen(false)] out T result) public bool TryPeek([MaybeNullWhen(false)] T result)
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
@ -212,4 +212,4 @@ namespace ObservableCollections
return GetEnumerator(); return GetEnumerator();
} }
} }
} }

View File

@ -6,7 +6,7 @@ using System.Linq;
namespace ObservableCollections namespace ObservableCollections
{ {
public class RingBuffer<T> : IList<T>, IReadOnlyList<T> public sealed class RingBuffer<T> : IList<T>, IReadOnlyList<T>
{ {
T[] buffer; T[] buffer;
int head; int head;

View File

@ -86,22 +86,12 @@ namespace ObservableCollections
{ {
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, bool> filter) public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, bool> filter)
{ {
source.AttachFilter(new SynchronizedViewValueOnlyFilter<T, TView>(filter)); source.AttachFilter(new SynchronizedViewFilter<T>(filter));
} }
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, TView, bool> filter) public static bool IsNullFilter<T>(this ISynchronizedViewFilter<T> filter)
{ {
source.AttachFilter(new SynchronizedViewFilter<T, TView>(filter)); return filter == SynchronizedViewFilter<T>.Null;
}
public static bool IsNullFilter<T, TView>(this ISynchronizedViewFilter<T, TView> filter)
{
return filter == SynchronizedViewFilter<T, TView>.Null;
}
internal static bool IsMatch<T, TView>(this ISynchronizedViewFilter<T, TView> filter, (T, TView) item)
{
return filter.IsMatch(item.Item1, item.Item2);
} }
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, (T value, TView view) value, int index) internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, (T value, TView view) value, int index)
@ -111,7 +101,7 @@ namespace ObservableCollections
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int index) internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int index)
{ {
var isMatch = collection.Filter.IsMatch(value, view); var isMatch = collection.Filter.IsMatch(value);
if (isMatch) if (isMatch)
{ {
filteredCount++; filteredCount++;
@ -154,7 +144,7 @@ namespace ObservableCollections
internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int oldIndex) internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int oldIndex)
{ {
var isMatch = collection.Filter.IsMatch(value, view); var isMatch = collection.Filter.IsMatch(value);
if (isMatch) if (isMatch)
{ {
filteredCount--; filteredCount--;
@ -184,7 +174,7 @@ namespace ObservableCollections
} }
else else
{ {
ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1); ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1);
} }
} }
} }
@ -198,7 +188,7 @@ namespace ObservableCollections
internal static void InvokeOnMove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int index, int oldIndex) internal static void InvokeOnMove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int index, int oldIndex)
{ {
// move does not changes filtered-count // move does not changes filtered-count
var isMatch = collection.Filter.IsMatch(value, view); var isMatch = collection.Filter.IsMatch(value);
if (isMatch) if (isMatch)
{ {
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex)); ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex));
@ -216,8 +206,8 @@ namespace ObservableCollections
internal static void InvokeOnReplace<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1) internal static void InvokeOnReplace<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1)
{ {
var oldMatched = collection.Filter.IsMatch(oldValue, oldView); var oldMatched = collection.Filter.IsMatch(oldValue);
var newMatched = collection.Filter.IsMatch(value, view); var newMatched = collection.Filter.IsMatch(value);
var bothMatched = oldMatched && newMatched; var bothMatched = oldMatched && newMatched;
if (bothMatched) if (bothMatched)

File diff suppressed because it is too large Load Diff