C# 如何关联功能输入和输出?

C# 如何关联功能输入和输出?,c#,system.reactive,C#,System.reactive,考虑下面的简单程序。它有一个可观察的整数和一个计算最近发布的整数是偶数还是奇数的函数。出乎意料的是,程序会在报告数字更改之前报告最近的数字是否为偶数/奇数 static void Main(string[] args) { int version = 0; var numbers = new Subject<int>(); IObservable<bool> isNumberEven = numbers.Select(i => i % 2 =

考虑下面的简单程序。它有一个可观察的整数和一个计算最近发布的整数是偶数还是奇数的函数。出乎意料的是,程序会在报告数字更改之前报告最近的数字是否为偶数/奇数

static void Main(string[] args) {
    int version = 0;
    var numbers = new Subject<int>();
    IObservable<bool> isNumberEven = numbers.Select(i => i % 2 == 0);
    isNumberEven
        .Select(i => new { IsEven = i, Version = Interlocked.Increment(ref version) })
        .Subscribe(i => Console.WriteLine($"Time {i.Version} : {i.IsEven}"));
    numbers
        .Select(i => new { Number = i, Version = Interlocked.Increment(ref version) })
        .Subscribe(i => Console.WriteLine($"Time {i.Version} : {i.Number}"));
    numbers.OnNext(1);
    numbers.OnNext(2);
    numbers.OnNext(3);
    Console.ReadLine();
}
我认为改变数量会引发一连串的下游效应,这些效应会按发生的顺序进行报告。交换订阅订单将交换报告结果的方式。我知道rx是异步的,事情有可能以不确定的顺序发生。如果我在函数中使用.Delay()或web调用,我无法确定何时报告结果。但在这种情况下,我很惊讶

这有什么大不了的?我认为这意味着,如果我想尝试将函数输入和输出关联起来(比如打印发布的数字以及它们是偶数还是奇数),我必须在输出结果中包含输入参数,如下所示:

var isNumberEven = numbers.Select(i => new {
    Number = i,
    IsEven = i % 2 == 0
});
我想我可以构建一组简单的小函数,然后使用rx操作符组合它们来完成复杂的计算。但也许我不能使用rx操作符来组合/加入/关联结果。定义每个函数时,我必须自己关联输入和输出

在某些情况下,我可以使用rx运算符关联结果。如果每个输入生成一个输出,我就可以压缩这两个。但是,一旦你做了一些像节流输入的事情,它就不再工作了

这个版本的程序似乎以合理的方式报告了数字是偶数还是奇数

static void Main(string[] args) {
    var numbers = new Subject<int>();
    var isNumberEven = numbers.Select(i => i % 2 == 0);
    var publishedNumbers = numbers.Publish().RefCount();
    var report =
        publishedNumbers
        .GroupJoin(
            isNumberEven,
            (_) => publishedNumbers,
            (_) => Observable.Empty<bool>(),
            (n, e) => new { Number = n, IsEven = e })
        .SelectMany(i => i.IsEven.Select(j => new { Number = i.Number, IsEven = j }));
    report.Subscribe(i => Console.WriteLine($"{i.Number} {(i.IsEven ? "even" : "odd")}"));
    numbers.OnNext(1);
    numbers.OnNext(2);
    numbers.OnNext(3);
    Console.ReadLine();
}

但我不知道这是否是一个幸运的巧合,也不知道我是否可以相信它。Rx中的哪些操作以确定性顺序发生?哪些是不可预测的?我是否应该定义所有函数,以便在结果中包含输入参数?

您的第一个程序的行为完全符合我的预期,并且是决定性的

我知道rx是异步的,事情有可能以不确定的顺序发生

只有在引入非确定性行为(如并发/调度)时,事情才会以非确定性顺序发生,否则Rx是确定性的

这里有几个问题/误解。 1) 可变外部状态-
版本
2) 主题的使用(但在本示例中根本不是问题) 3) 对如何发出回调的误解

让我们只关注3)。如果我们把你的代码打开到它的基本调用站点,你可能会看到封面下的Rx是多么简单

number.OnNext(1)
主题将查找其订阅,并按订阅顺序查找每个订阅

IObservable<bool> isNumberEven = numbers.Select(i => i % 2 == 0);
isNumberEven
    .Select(i => new { IsEven = i, Version = Interlocked.Increment(ref version) })
    .Subscribe(i => Console.WriteLine($"Time {i.Version} : {i.IsEven}"));
有人可能会说,
isNumberEven
从未在其他任何地方使用过,因此您应该将其简化为此

所以我们可以看到我们有了第一个订户。 实际上,它将运行的代码是

private void HandleOnNext(int i)
{
    var isEven = i % 2 == 0
    var temp = new { IsEven = isEven , Version = Interlocked.Increment(ref version) };
    Console.WriteLine($"Time {temp .Version} : {temp .IsEven}");
}
我们的第二个订阅者(因为
.Subscribe(
方法是在偶数订阅之后调用的)是
numbers
订阅者。 他的代码可以有效地归结为

private void HandleOnNext(int i)
{
    var temp = new { Number = i, Version = Interlocked.Increment(ref version) };
    Console.WriteLine($"Time {temp.Version} : {temp.Number}");
}
所以,一旦你完全解构了代码,你基本上会得到这样的结果

void Main()
{
    int version = 0;

    //numbers.OnNext(1);
    ProcessEven(1, ref version);
    ProcessNumber(1, ref version);
    //numbers.OnNext(2);
    ProcessEven(2, ref version);
    ProcessNumber(2, ref version);
    //numbers.OnNext(3);
    ProcessEven(3, ref version);
    ProcessNumber(3, ref version);
}

// Define other methods and classes here
private void ProcessEven(int i, ref int version)
{
    var isEven = i % 2 == 0;
    var temp = new { IsEven = isEven, Version = Interlocked.Increment(ref version) };
    Console.WriteLine($"Time {temp.Version} : {temp.IsEven}");
}
private void ProcessNumber(int i, ref int version)
{
    var temp = new { Number = i, Version = Interlocked.Increment(ref version) };
    Console.WriteLine($"Time {temp.Version} : {temp.Number}");
}
一旦所有回调和订阅都被具体化,那么您就可以看到这并不是什么神奇的事情,一切都是确定性的

我是否应该定义所有函数以在结果中包含输入参数

为了回答您的问题(鉴于您对Rx的误解,我不太愿意回答),您只需要在结果序列的顺序不确定时回答。 一次发出多个web请求可能就是一个例子。 您无法确定他们是否都会按照您发送的顺序响应。
但是,你可以强制这些场景与运营商的使用保持一致,比如
Concat

哇,这非常有用。今天早些时候,我花了99美分在亚马逊上买了你的书。谢谢!我向你保证,巨额利润将用于……几乎涵盖网站的运行。
private void HandleOnNext(int i)
{
    var isEven = i % 2 == 0
    var temp = new { IsEven = isEven , Version = Interlocked.Increment(ref version) };
    Console.WriteLine($"Time {temp .Version} : {temp .IsEven}");
}
private void HandleOnNext(int i)
{
    var temp = new { Number = i, Version = Interlocked.Increment(ref version) };
    Console.WriteLine($"Time {temp.Version} : {temp.Number}");
}
void Main()
{
    int version = 0;

    //numbers.OnNext(1);
    ProcessEven(1, ref version);
    ProcessNumber(1, ref version);
    //numbers.OnNext(2);
    ProcessEven(2, ref version);
    ProcessNumber(2, ref version);
    //numbers.OnNext(3);
    ProcessEven(3, ref version);
    ProcessNumber(3, ref version);
}

// Define other methods and classes here
private void ProcessEven(int i, ref int version)
{
    var isEven = i % 2 == 0;
    var temp = new { IsEven = isEven, Version = Interlocked.Increment(ref version) };
    Console.WriteLine($"Time {temp.Version} : {temp.IsEven}");
}
private void ProcessNumber(int i, ref int version)
{
    var temp = new { Number = i, Version = Interlocked.Increment(ref version) };
    Console.WriteLine($"Time {temp.Version} : {temp.Number}");
}