在使用异步扩展方法时,请订阅可观察的可观察到的



对于我正在使用ReactiveUI的新项目。我开始非常喜欢反应性编程背后的想法,但是仍然很难缠绕一些概念并编写惯用代码。

在此示例中,我有一个非常基本的MainWindow,其中一个名为" UserId"的文本框:

<Window
    x:Class="ReactiveUiDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"
    Width="800"
    Height="450">
    <StackPanel>
        <TextBox x:Name="UserId" />
    </StackPanel>
</Window>

文本框绑定到MainWindow构造函数的相应ViewModel上的属性:

using System.Reactive.Disposables;
using System.Windows;
using ReactiveUI;
namespace ReactiveUiDemo
{
    public partial class MainWindow : Window, IViewFor<MainWindowViewModel>
    {
        public MainWindowViewModel ViewModel { get; set; }
        object IViewFor.ViewModel
        {
            get => ViewModel;
            set => ViewModel = (MainWindowViewModel)value;
        }
        public MainWindow()
        {
            InitializeComponent();
            ViewModel = new MainWindowViewModel();
            this.WhenActivated(disposables =>
            {
                this
                    .Bind(ViewModel, vm => vm.UserId, v => v.UserId.Text)
                    .DisposeWith(disposables);
            });
        }
    }
}

然后,观察此属性的视图模型(ReactiveUi.Fody [Reactive]属性应用于该属性(。输入4位数字后,它会尝试查找用户,该用户在用户ID 1234或失败时成功。然后使用消息框显示此结果。

using System;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ReactiveUiDemo
{
    public sealed class MainWindowViewModel : ReactiveObject
    {
        [Reactive]
        public string UserId { get; private set; }
        public MainWindowViewModel()
        {
            this
                .WhenAnyValue(t => t.UserId)
                .Where(u => (u?.Length ?? 0) == 4)
                .Where(u => int.TryParse(u, out _))
                .Select(i => int.Parse(i))
                .Select(i => GetUserName(i))
                .Match(
                    userName => DisplaySuccess(userName),
                    failure => DisplayError(failure))
                .Do(_ => UserId = string.Empty)
                .Subscribe(
                    _ => MessageBox.Show("OnNext"),
                    _ => MessageBox.Show("OnError"),
                    () => MessageBox.Show("OnCompleted"));
        }
        private enum Failure { UserNotFound }
        private Result<string, Failure> GetUserName(int userId)
        {
            if (userId == 1234)
                return "Waldo";
            return Failure.UserNotFound;
        }
        private async Task<Unit> DisplayError(Failure failure)
        {
            MessageBox.Show($"Error: {failure}.");
            await Task.CompletedTask;
            return Unit.Default;
        }
        private async Task<Unit> DisplaySuccess(string userName)
        {
            MessageBox.Show($"Found {userName}!");
            await Task.CompletedTask;
            return Unit.Default;
        }
    }
}

类"结果"(或此剥离的版本(包含tsuccess或tfailure:

using System;
namespace ReactiveUiDemo
{
    public sealed class Result<TSuccess, TFailure>
    {
        private readonly bool _isSuccess;
        private readonly TSuccess _success;
        private readonly TFailure _failure;
        private Result(TSuccess value)
        {
            _isSuccess = true;
            _success = value;
            _failure = default;
        }
        private Result(TFailure value)
        {
            _isSuccess = false;
            _success = default;
            _failure = value;
        }
        public TResult Match<TResult>(Func<TSuccess, TResult> successFunc, Func<TFailure, TResult> failureFunc)
            => _isSuccess ? successFunc(_success) : failureFunc(_failure);
        public static implicit operator Result<TSuccess, TFailure>(TSuccess value)
            => new Result<TSuccess, TFailure>(value);
        public static implicit operator Result<TSuccess, TFailure>(TFailure value)
            => new Result<TSuccess, TFailure>(value);
    }
}

让我头痛的扩展方法是匹配方法,其定义如下:

using System;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;
namespace ReactiveUiDemo
{
    public static class ObservableExtensions
    {
        public static IObservable<TResult> Match<TSuccess, TFailure, TResult>(
            this IObservable<Result<TSuccess, TFailure>> source,
            Func<TSuccess, Task<TResult>> success,
            Func<TFailure, Task<TResult>> failure)
            => Observable.FromAsync(async () => await source.SelectMany(result => result.Match(success, failure).ToObservable()));
    }
}

代码在匹配扩展方法之前按预期工作。Displayerror或DisplaySuccess被称为,但这就是其中的终点;DO和订阅中的操作未执行。我相信我的比赛扩展方法存在问题,但我不知道如何解决。

作为旁注,我想有一种更好的写作方法:

                .Where(u => (u?.Length ?? 0) == 4)
                .Where(u => int.TryParse(u, out _))
                .Select(int.Parse)

我可以想象一种TryParseint扩展方法,但也许不需要?

编辑

根据@glennwatson的答案更新了扩展方法,它可以正常工作:

public static IObservable<TResult> Match<TSuccess, TFailure, TResult>(
    this IObservable<Result<TSuccess, TFailure>> source,
    Func<TSuccess, Task<TResult>> success,
    Func<TFailure, Task<TResult>> failure)
    => source.SelectMany(r => r.Match(success, failure).ToObservable());

fromasync((方法仅用于基于任务的系统。

使用可观察和后续的LINQ样式方法时,请尽可能地保持可观察的形式。

在您的示例中,您正在等待可观察到的,并在fromasync中包装它。SelectMany的过载可以了解基于任务的操作。

相关内容

  • 没有找到相关文章

最新更新