【发布时间】:2021-05-11 07:20:50
【问题描述】:
我最近被一个programming exercise 难住了,它要求使用“反应式编程”。
问题陈述很简单:
- 使用级联值表达式实现“单元格”(很像 Excel)
- “输入单元格” - 具有静态值分配的单元格(即
Value = 1) - “计算单元格”- 具有依赖单元格列表和 lambda 表达式以计算值的单元格(即
(array cells) => cells[0] - cell[1])
- “输入单元格” - 具有静态值分配的单元格(即
我的(有缺陷的)实现如下所示:
public abstract class Cell
{
protected int _value;
public virtual int Value
{
get {
return _value;
}
set {
if(_value != value)
{
_value = value;
OnChanged();
}
}
}
public event EventHandler<int> Changed;
public void OnChanged()
{
if(Changed is EventHandler<int> handler)
handler(this, Value);
}
}
public class InputCell : Cell
{
public InputCell() : this(0)
{
}
public InputCell(int value)
{
Value = value;
}
}
public class ComputeCell : Cell
{
public ComputeCell(IEnumerable<Cell> producers, Func<int[], int> compute)
{
_producers = new List<Cell>(producers);
_compute = compute;
_operands = new int[_producers.Count];
for(int i = 0; i < _operands.Length; i++)
{
var producer = _producers[i];
_operands[i] = producer.Value;
producer.Changed += new EventHandler<int>(Update);
}
}
private List<Cell> _producers;
private Func<int[], int> _compute;
private int[] _operands;
public override int Value { get { return _compute(_operands); } set {} }
private void Update(object sender, int newValue)
{
var was = Value;
for(int i = 0; i < _operands.Length; i++)
_operands[i] = _producers[i].Value;
if(was != Value)
OnChanged();
}
}
这对大量输入/测试用例“有效”,但最后一个测试用例失败:
var input = new InputCell(1);
var plusOne = new ComputeCell(new[] { input }, inputs => inputs[0] + 1);
var minusOne = new ComputeCell(new[] { input }, inputs => inputs[0] - 1);
var alwaysTwo = new ComputeCell(new[] { plusOne, minusOne }, inputs => inputs[0] - inputs[1]);
// change value of dependent input cell
input.Value = 2
然后测试尝试断言 alwaysTwo 是否引发更改通知 - 不应该,因为输入值的更改对 alwaysTwo 的最终值没有影响。
但由于明显的竞争条件,我的实现引发了两个更改通知:
-
inputCell--通知-->plusOne--通知-->alwaysTwo
alwaysTwo 然后“认为”它的结果值已经改变,因为我们在等待第一个通知一直传播时阻止了对minusTwo 的通知。
inputCell
/ \
/ \
/ \
v v
plusTwo minusTwo
\ /
\ /
\ /
v v
alwaysTwo
换句话说,我最终得到了这个菱形图,并且所有北->南边路径总是在其他任何事情之前进行端到端评估。
如何避免不必要地从alwaysTwo 发出通知(即延迟到所有相关单元格都更新后)?
wikipedia page for reactive programming 提到了这类明显的问题,并建议在传播更改之前对依赖表达式进行拓扑排序,但我很难弄清楚如何/在哪里应用它。
【问题讨论】:
标签: c# event-handling reactive-programming