Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ This library provides:

- A `ReactiveList<T>` implementation that allows you to observe changes in real-time.
- A `Reactive2DList<T>` for managing a list of lists (2D structure) with reactive capabilities.

> Net 8 and above also includes:
- A `QuaternaryDictionary<TKey, TValue>` for high-performance key-value storage with reactive features.
- A `QuaternaryList<T>` for optimized list operations at scale with reactive capabilities.
- A `QuaternaryList<T>` for optimized list like operations at scale with reactive capabilities.

Here's a quick example to get you started:

Expand Down Expand Up @@ -883,6 +885,7 @@ High-performance, thread-safe, sharded collections optimized for large-scale rea

## QuaternaryList&lt;T&gt; API Reference


### Properties

| Property | Type | Description |
Expand All @@ -904,7 +907,20 @@ High-performance, thread-safe, sharded collections optimized for large-scale rea
| `Contains(T item)` | Check if item exists (O(1) average) |
| `Edit(Action<IList<T>> editAction)` | Batch operations with single notification |
| `AddIndex<TKey>(string name, Func<T, TKey> keySelector)` | Add secondary index |
| `Query<TKey>(string indexName, TKey key)` | Query using secondary index |
| `GetItemsBySecondaryIndex<TKey>(string indexName, TKey key)` | Query using secondary index |
| `CopyTo(T[] array, int arrayIndex)` | Copy all items to an array |
| `GetEnumerator()` | Enumerate all items across shards |

### Unsupported Methods (Due to Sharded Architecture)

| Method | Alternative |
|--------|-------------|
| `IndexOf(T item)` | Use `Contains(item)` to check existence |
| `Insert(int index, T item)` | Use `Add(item)` - sharding determines placement |
| `RemoveAt(int index)` | Use `Remove(item)` or `RemoveMany(predicate)` |
| `this[int index] { set; }` | Use `Remove` + `Add` for updates |

> **Note**: QuaternaryList distributes items across 4 shards based on hash code. Index-based operations would be misleading since item order is determined by the sharding algorithm, not insertion order.

### Secondary Indices

Expand All @@ -921,8 +937,8 @@ list.AddIndex("ByDepartment", c => c.Department);
list.AddRange(contacts);

// Fast O(1) query by index
var newYorkers = list.Query("ByCity", "New York");
var engineers = list.Query("ByDepartment", "Engineering");
var newYorkers = list.GetItemsBySecondaryIndex("ByCity", "New York");
var engineers = list.GetItemsBySecondaryIndex("ByDepartment", "Engineering");
```

---
Expand Down Expand Up @@ -1104,20 +1120,27 @@ sourceList.AddRange(people);
sourceList.Edit(list =>
{
list.Add(new Person("John"));
list.RemoveAt(0);
list.RemoveAt(0); // SourceList supports index-based removal
});
sourceList.Connect()
.Filter(p => p.Age > 18)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(changes => { });

// QuaternaryList equivalent
// Note: QuaternaryList is sharded, so index-based operations are not supported.
// Use item-based Remove() or RemoveMany() instead of RemoveAt().
var quaternaryList = new QuaternaryList<Person>();
quaternaryList.AddRange(people);
quaternaryList.Edit(list =>
{
list.Add(new Person("John"));
list.RemoveAt(0);
// Use Remove(item) instead of RemoveAt(index)
var firstItem = list.FirstOrDefault();
if (firstItem != null)
{
list.Remove(firstItem);
}
});
quaternaryList.Stream
.Where(n => n.Action == CacheAction.Added && n.Item?.Age > 18)
Expand Down Expand Up @@ -1146,6 +1169,7 @@ sourceCache.Connect()
.Bind(out var activeUsers)
.Subscribe();


// QuaternaryDictionary equivalent
var quaternaryDict = new QuaternaryDictionary<Guid, Person>();
quaternaryDict.AddOrUpdate(person.Id, person);
Expand Down Expand Up @@ -1173,9 +1197,13 @@ quaternaryDict.CreateView(p => p.IsActive, RxApp.MainThreadScheduler)
| `Watch()` | Subscribe to `Stream` | Filter by key in subscription |
| `SourceCache<T, TKey>` | `QuaternaryDictionary<TKey, T>` | Key selector not needed |
| `AddOrUpdate(item)` | `AddOrUpdate(key, value)` | Explicit key required |
| `RemoveAt(index)` | `Remove(item)` | Index-based ops not supported (sharded) |
| `Insert(index, item)` | `Add(item)` | Index-based ops not supported (sharded) |
| `IndexOf(item)` | `Contains(item)` | Use Contains for existence check |

---


## Advanced Example: Address Book Application

```csharp
Expand Down Expand Up @@ -1229,7 +1257,7 @@ public class AddressBookViewModel : IDisposable
public void BulkRemoveByDepartment(string department)
{
// O(1) query using secondary index
var targets = _contactList.Query("ByDepartment", department).ToList();
var targets = _contactList.GetItemsBySecondaryIndex("ByDepartment", department).ToList();

// Bulk thread-safe remove
_contactList.RemoveRange(targets);
Expand All @@ -1244,7 +1272,7 @@ public class AddressBookViewModel : IDisposable
public void UpdateCityName(string oldCity, string newCity)
{
// Fast index query
var targets = _contactList.Query("ByCity", oldCity).ToList();
var targets = _contactList.GetItemsBySecondaryIndex("ByCity", oldCity).ToList();

// Create updated records
var updates = targets.Select(c =>
Expand Down
2 changes: 1 addition & 1 deletion src/ReactiveList.Benchmarks/QuaternaryListBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public int QuaternaryList_QueryIndex()
using var list = new QuaternaryList<int>();
list.AddIndex("Mod2", x => x % 2);
list.AddRange(_data);
return list.Query("Mod2", 0).Count();
return list.GetItemsBySecondaryIndex("Mod2", 0).Count();
}

[Benchmark]
Expand Down
6 changes: 5 additions & 1 deletion src/ReactiveList.Test/CacheActionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public void CacheAction_ShouldHaveCorrectValues()
((int)CacheAction.Updated).Should().Be(2);
((int)CacheAction.Cleared).Should().Be(3);
((int)CacheAction.BatchOperation).Should().Be(4);
((int)CacheAction.BatchAdded).Should().Be(5);
((int)CacheAction.BatchRemoved).Should().Be(6);
}

/// <summary>
Expand All @@ -35,12 +37,14 @@ public void CacheAction_AllValuesShouldBeDefined()
{
var values = Enum.GetValues<CacheAction>();

values.Should().HaveCount(5);
values.Should().HaveCount(7);
values.Should().Contain(CacheAction.Added);
values.Should().Contain(CacheAction.Removed);
values.Should().Contain(CacheAction.Updated);
values.Should().Contain(CacheAction.Cleared);
values.Should().Contain(CacheAction.BatchOperation);
values.Should().Contain(CacheAction.BatchAdded);
values.Should().Contain(CacheAction.BatchRemoved);
}
}
#endif
Loading