.net 如何将一个动态的观测值集合合并成一个新的观测值?

weylhg0b  于 2023-02-06  发布在  .NET
关注(0)|答案(2)|浏览(136)

在我的程序中,我有Room的概念,每个房间都有一些User,每个用户都有一个Observable,表示他想要房间里的安静程度,当用户选择一个新值时,我只需在User类中的内部BehaviorSubject示例上调用OnNext,将其推送到Observable。
这个程序的主要逻辑是,用户应该能够知道房间需要“什么程度的安静”,例如,如果房间里至少有一个用户需要安静,那么整个房间都需要安静。
这是我的类的一个简化:
首先,我使用一个枚举来表示可能的声级,现在只有两个值:

enum SoundLevelRequirement
{
    /// <summary>
    ///     Indicates that the user is ok with any sound level in the room.
    /// </summary>
    None,

    /// <summary>
    ///     Indicates that the user needs absolute silence in the room.
    /// </summary>
    Silence
}

用户只需要公开一个Observable,每当他的状态发生变化时,Observable就会发出滴答声。

class User
{
    private readonly BehaviorSubject<SoundLevelRequirement> soundLevel;

    public User(string name)
    {
        Name = name;
        soundLevel = new BehaviorSubject<SoundLevelRequirement>(SoundLevelRequirement.None);
    }

    public string Name { get; }

    public IObservable<SoundLevelRequirement> SoundLevelChanged => soundLevel.DistinctUntilChanged();

    public void SetSoundLevel(SoundLevelRequirement level)
    {
        soundLevel.OnNext(level);
    }
}

而房间类基本上是用户的一个容器,应该有自己的可观察项来表示整个房间的整体状态:

class Room
{
    private readonly BehaviorSubject<SoundLevelRequirement> soundLevel;
    private readonly ObservableCollection<User> users;

    public Room(string name)
    {
        Name = name;
        Users = new ObservableCollection<User>();

        // THIS IS WHERE I NEED TO COMBINE ALL CHILD OBSERVABLES INSTEAD 
        // OF USING ANOTHER Subject
        soundLevel = new BehaviorSubject<SoundLevelRequirement>(SoundLevelRequirement.None);
    }

    public ObservableCollection<User> Users { get; set; }

    public string Name { get; }

    public IObservable<SoundLevelRequirement> SoundLevel => soundLevel.DistinctUntilChanged();
}

我在将使用Rx的用户Observable直接合并到另一个Observable时遇到了麻烦,因为解决方案的动态特性。(新的User对象可以添加到房间或从房间中移除),所以我没有一个静态的可观察对象列表可以使用。通过利用CombineLatest,然后在其上执行我的过滤逻辑,可以非常容易地实现,如下所示:

Users.Select(u => u.SoundLevelChanged).CombineLatest().Select(latest => latest.Max());

这样,每当用户状态发生变化时,我只需要看看“最大价值”是什么,就可以确定房间的状态,但一旦我添加或删除用户,我就需要让它与可观察到的同步,所以这是行不通的。
我还需要确保在用户离开房间时进行适当的处理。例如,如果一个5人的房间由于一个用户选择了Silence而处于“Silence”状态,则必须在该用户离开时重置房间状态,并将其设置回None
我曾考虑过使用ObservableCollection来监视添加和删除,但我无法在不重新创建可观察对象的情况下提出一些东西,这显然是行不通的,因为已经有人订阅了更改。

ljsrvy3e

ljsrvy3e1#

如果可以的话,我会尽量简化,只需对类User做一个简单的修改即可。
用户需要添加此属性:

public SoundLevelRequirement SoundLevel => soundLevel.Value;

现在可以像这样实现Room

class Room
{
    private readonly IObservable<SoundLevelRequirement> soundLevel;

    public Room(string name)
    {
        this.Name = name;
        this.Users = new ObservableCollection<User>();

        soundLevel =
            Observable
                .Create<SoundLevelRequirement>(o =>
                    Observable
                        .FromEventPattern<
                            NotifyCollectionChangedEventHandler,
                            NotifyCollectionChangedEventArgs>(
                            h => this.Users.CollectionChanged += h,
                            h => this.Users.CollectionChanged -= h)
                        .SelectMany(x => this.Users
                            .Select(u => u.SoundLevelChanged
                                .StartWith(u.SoundLevel))
                                .Merge())
                        .Select(ep =>
                            this.Users.Any(u =>
                                u.SoundLevel == SoundLevelRequirement.Silence)
                                    ? SoundLevelRequirement.Silence
                                    : SoundLevelRequirement.None)
                        .DistinctUntilChanged()
                        .Subscribe(o));
    }

    public ObservableCollection<User> Users { get; set; }

    public string Name { get; }

    public IObservable<SoundLevelRequirement> SoundLevel => soundLevel.AsObservable();
}

如果您需要让Room.SoundLevel具有重播1,则将.Replay(1)添加到soundLevel字段中,但它必须成为IConnectableObservable<SoundLevelRequirement>才能使其正常工作。然后,您需要在Room上实现IDisposable以释放连接。这是正确的方法-您应该尽可能避免主题。它们我只会让你的代码更难处理。

xxb16uws

xxb16uws2#

我知道这个问题已经很老了,但是我想指出的是,使用动态数据中的扩展方法,可以用一种很简单的方式编写这个问题。
财产

public SoundLevelRequirement SoundLevel => soundLevel.Value;

需要按照Enigmativity的建议添加到User类中,然后您只需编写

soundLevel = this.Users
        .ToObservableChangeSet()
        .MergeMany(user => user.SoundLevelChanged)
        .Select(_ => this.Users.Select(u => u.SoundLevel).Max());

Room类的构造函数中。

相关问题