在c#中实现反应式编程/函数

时间:2012-02-09 14:22:41

标签: c# reactive-programming

我最近正在阅读有关游戏引擎设计的内容,并最终绊倒了这个:What is (functional) reactive programming?

我想知道如何实现第二高评级答案中给出的示例。在C ++中,很容易将指针传递给存储鼠标坐标的值,只返回值而不是int。好吧,我们不能在C#中真正做到这一点,所以这是我们的第一个问题。我们是否需要调用一些“更新”功能来保持所有值的最新状态?

其次如何处理语法?分配值是直截了当的。但是,每当我要求它时,“让鼠标定位并从中取出14分钟”会稍微复杂一些......

最后,我想知道如何直接引用C#中的任何对象返回值。例如

int test = 1;

测试将返回1.所以我可以做1 + test之类的事情,这将是= 2

但如果我有一个

的实例
public class ReactiveInt {
     int myValue
}

在尝试添加int时,我不能只做我上面做的事。

对不起我想这么广泛的问题。如果可以给出一个简单的例子,演示类似于答案中讨论的功能,我想我的所有问题都会得到解答..

3 个答案:

答案 0 :(得分:8)

问题

问题1

  

在尝试添加int时,我不能只做我上面做的事。

嗯,这实际上就是重点。在反应式编程中,您不希望强制性地将两个数字相加,而是希望根据其他数字定义新数字。因此,即使c = a + bc发生变化,a + ba 总是等于bc }对ab具有反应性。

var a = new BehaviorSubject(3);
var b = new BehaviorSubject(1);
var c = Rx.Observable.combineLatest(a, b, function(vals) {
    return vals[0] + vals[1];
});

问题2

  

我想知道如何实现第二个最高评级答案中给出的例子。

最简单的答案列表和haskell中的高阶函数。

回答你不想要功能反应式编程违背你在命令式编程中学到的一切,你将不得不重新学习如何KnockoutJS,当你可以在几百行中使用RxJS-Splash之类的东西做同样的事情,如果你使用FRP开始。 (注意Splash是如何基于Rx的,它是可重用的代码,而Knockout是纯粹的特定于实现的代码)。

FRP具有事件时间的概念,而依赖关系跟踪仅包含更改的概念。功能反应代码与命令式代码一样长。它不是“建立在命令式代码之上”。 (是的,它仍然汇编到汇编...而不是重点),它的概念从根本上不同。

实施例

使用Microsoft的JavaScript Reactive Extensions(RxJS

请注意,Rx现在提供多种语言版本,包括原生C++

示例的直接端口

var moves = $(document).onAsObservable('mousemove')
    .map(function(e) {
        return {
            x: e.pageX,
            y: e.pageY
        };
    });

var xs = moves.map(function(move) { return move.x; });
var ys = moves.map(function(move) { return move.y; });

var minXs = xs.map(function(x) { return x - 16; });
var minYs = ys.map(function(y) { return y - 16; });
var maxYs = xs.map(function(x) { return x + 16; });
var maxYs = ys.map(function(y) { return y + 16; });

var boundingRect = Rx.Observable.combineLatest(minXs, minYs, maxXs, maxYs)
    .map(function(vals) {
        var minX = vals[0];
        var minY = vals[1];
        var maxX = vals[2];
        var maxY = vals[3];

        return new Rectangle(minX, minY, maxX, maxY);
    });

简化端口

由于矩形仅根据一个相关值(或事件)定义,因此您可以将其简化为以下内容:

var boundingRect = $(document).onAsObservable('mousemove')
    .map(function(e) {
        var x = e.pageX;
        var y = e.pageY;
        return new Rectangle(x - 16, y - 16, x + 16, y + 16);
        });

使用

此时您可以使用它来组成其他可观察序列(随时间变化的值)。

var area = boundingRect.map(function(rect) {
    return rect.getSize();
    });

或直接订阅。

boundingRect.subscribe(function (rect) {
    // perform some action with the rect each time it changes.
    console.log(rect);
    });

但这只是在它发生变化时才会发生!

如果我们在订阅后想要获得最新值,而不是等待矩形再次更改,该怎么办?嗯,这就是BehaviorSubject进来的地方。

var rects = new Rx.BehaviorSubject(new Rectangle(-1, -1, 1, 1));

rects.subscribe(function(rect) {
        // this would happen twice in this example.
        // Once for the initial value (above), and once when it is changed later (below).
        console.log(rect); 
    });

rects.onNext(new Rectangle(-1, -1, 1, 1));

但那不是最初的观察,而且功能不是很好......

以下是如何使用一些内置功能将原始observable做同样的事情,将Observable更改为像BehaviorSubject这样的行为......

var rectsAndDefault = rects.startWith(new Rectangle()); // just give it an initial value
rectsAndDefault.subscribe(function(rect) {
    console.log(rect); // happens once for the "startWith" rectangle, and then again for all subsequent changes
    })

同样,FRP也不同。它有很大的不同,但这是一项值得学习的大事。你会知道,当它开始让你大开眼界时,你已经开始接受了它。

答案 1 :(得分:1)

答案 2 :(得分:0)

使用Lambdas和Actions作为事件处理程序,您可以在没有指针的情况下离开。

int Offset = 16;
int Size = Offset * 2;
Action<MouseEventArgs> MouseEventArgsHandler = (args) => DrawRectangle(args.x - Offset, args.y - Offset, Size, Size);