< Summary

Information
Class: MyNet.Observable.MergeManyEx<T1, T2>
Assembly: MyNet.Observable
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Extensions/MergeManyEx.cs
Tag: 323_28699572109
Line coverage
100%
Covered lines: 28
Uncovered lines: 0
Coverable lines: 28
Total lines: 103
Line coverage: 100%
Branch coverage
50%
Covered branches: 2
Total branches: 4
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%44100%
Run()100%11100%
ForwardWhenRemove(...)100%11100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Extensions/MergeManyEx.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="MergeManyEx.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Linq;
 9using System.Reactive.Linq;
 10using DynamicData;
 11
 12#pragma warning disable IDE0130 // Namespace does not match folder structure
 13namespace MyNet.Observable;
 14#pragma warning restore IDE0130 // Namespace does not match folder structure
 15
 16/// <summary>
 17/// Forwards inner change sets when outer items are added and synthesizes remove change sets when outer items are remove
 18/// </summary>
 19/// <remarks>
 20/// <paramref name="observableSelector"/> is invoked again on outer remove; it must return the same inner snapshot
 21/// for the same outer item (no side effects, stable child collection).
 22/// </remarks>
 23internal sealed class MergeManyEx<T, TDestination>(IObservable<IChangeSet<T>> source,
 24    Func<T, IObservable<IChangeSet<TDestination>>> observableSelector)
 25    where T : notnull
 26    where TDestination : notnull
 27{
 628    private readonly IObservable<IChangeSet<T>> _source = source ?? throw new ArgumentNullException(nameof(source));
 629    private readonly Func<T, IObservable<IChangeSet<TDestination>>> _observableSelector = observableSelector ?? throw ne
 30
 631    public IObservable<IChangeSet<TDestination>> Run() => System.Reactive.Linq.Observable.Create<IChangeSet<TDestination
 632        observer =>
 633        {
 634            var locker = new object();
 635            return _source
 636
 637                // SubscribeMany will forward initial cascaded inner items when it observers new item,
 638                // but if one item was removed, any item belonged to it won't be forwarded to observer.
 639                .SubscribeMany(t => _observableSelector(t).Synchronize(locker).Subscribe(observer.OnNext))
 640
 641                // So I add an observer here to subscribe all items belonged to the removed item, buildup a
 642                // ChangeSet and forward it to the original observer.
 643                .Subscribe(t =>
 644                {
 645                    foreach (var item in t.GetRemovedItems()) ForwardWhenRemove(observer, item);
 646                },
 647                observer.OnError);
 648        });
 49
 50    private void ForwardWhenRemove(IObserver<IChangeSet<TDestination>> observer, T sourceItem)
 51    {
 652        var observableList = _observableSelector(sourceItem).AsObservableList();
 653        var changeset = new ChangeSet<TDestination>(
 654        [
 655            new(ListChangeReason.RemoveRange, observableList.Items)
 656        ]);
 657        observableList.Dispose();
 658        observer.OnNext(changeset);
 659    }
 60}
 61
 62/// <inheritdoc cref="MergeManyEx{T, TDestination}"/>
 63internal sealed class MergeManyEx<T, TKey, TDestination, TDestinationKey>(IObservable<IChangeSet<T, TKey>> source,
 64    Func<T, IObservable<IChangeSet<TDestination, TDestinationKey>>> observableSelector,
 65    Func<TDestination, TDestinationKey> observableKeySelector)
 66    where T : notnull
 67    where TDestination : notnull
 68    where TKey : notnull
 69    where TDestinationKey : notnull
 70{
 71    private readonly IObservable<IChangeSet<T, TKey>> _source = source ?? throw new ArgumentNullException(nameof(source)
 72    private readonly Func<T, IObservable<IChangeSet<TDestination, TDestinationKey>>> _observableSelector = observableSel
 73    private readonly Func<TDestination, TDestinationKey> _observableKeySelector = observableKeySelector ?? throw new Arg
 74
 75    public IObservable<IChangeSet<TDestination, TDestinationKey>> Run() => System.Reactive.Linq.Observable.Create<IChang
 76        observer =>
 77        {
 78            var locker = new object();
 79            return _source
 80
 81                // SubscribeMany will forward initial cascaded inner items when it observers new item,
 82                // but if one item was removed, any item belonged to it won't be forwarded to observer.
 83                .SubscribeMany(t => _observableSelector(t).Synchronize(locker).Subscribe(observer.OnNext))
 84
 85                // So I add an observer here to subscribe all items belonged to the removed item, buildup a
 86                // ChangeSet and forward it to the original observer.
 87                .Subscribe(t =>
 88                {
 89                    foreach (var item in t.GetRemovedItems()) ForwardWhenRemove(observer, item);
 90                },
 91                observer.OnError);
 92        });
 93
 94    private void ForwardWhenRemove(IObserver<IChangeSet<TDestination, TDestinationKey>> observer, T sourceItem)
 95    {
 96        var observableList = _observableSelector(sourceItem).AsObservableCache();
 97        var changeset = new ChangeSet<TDestination, TDestinationKey>();
 98        changeset.AddRange(observableList.Items.Select(x => new Change<TDestination, TDestinationKey>(ChangeReason.Remov
 99        observableList.Dispose();
 100        observer.OnNext(changeset);
 101    }
 102}
 103