1

示例 1

正如我们所知,数据库中有计算列的概念,其中列是根据相邻列的值计算的。

问题是计算列不能与当前行之外的其他行相关。

示例 2

然后我们有电子表格(即 Excel),其中一个单元格可以有一个公式。这类似于计算列,但更强大。公式可以与电子表格中的任何单元格(或一组单元格)相关,而不仅仅是 RDB 中的当前行/列。

问题

计算(或自动更新值)的概念很棒,但我如何在 C# 中的一组值/对象中做类似的事情?

我想创建一个值列表(或数组),其中每个值都与同一列表中的其他(或一组)值相关?这就像电子表格单元格值......更改一个值并且相关的值随之更改(以及整个相关的子树值)。

C#中有这样的概念吗?

我将如何以最聪明的方式做到这一点?我知道我可以有一个LinkedList对象,其中对象的属性值将与同一列表中的其他对象相关,并在访问值时进行评估(按需评估)。这可能意味着在此过程中会评估几个(所有祖先节点)。有没有更好的方法可以更像电子表格单元格,它们首先被评估然后单独访问(传播评估)?

这当然也应该适用于多维数组。

4

4 回答 4

3

这个概念称为反应式编程。.NET 有一个叫做反应式扩展的东西,它可以让你实现你所描述的。具体来说,您需要使用称为Behavior的东西

于 2011-10-24T09:15:13.283 回答
2

您可以创建一个带有 indexer 属性的对象,该属性将包含您的计算:

class Foo {
    public int this[int index] {
        get {
            return index*2; //Your computation here
        }
    }
}

或者

class Foo {
    public int this[int row,int col] {
        get {
            return row*col; //Your computation here
        }
    }
}

或者,如果您想使用链表或类似的东西,您可以将“Cell”对象存储在列表结构中,并在这些“Cell”对象上有一个属性或方法来执行计算:

class Cell {
    private int _row;
    private int _col;

    public Cell(int row,int col) {
        _row = row;
        _col = col;
    }

    public int Value {
        get {
            return _row * _col;
        }
    }
}
于 2011-10-24T09:17:18.460 回答
1

您可以创建自己的数组类,它采用函数而不是值:

class ComputedArray<T>
{
    private Func<T>[] _array;

    public T this[int index] { get { return _array[index]( ); } }

    public void Set(int index, Func<T> func)
    {
        _array[index] = func;
    }

    public ComputedArray( int size )
    {
        _array = new Func<T>[size];
    }
}

现在您可以使用 lambda 表达式存储值:

ComputedArray<int> ar = new ComputedArray<int>( 2 );
ar.Set( 0, ( ) => 2 );
ar.Set( 1, ( ) => ar[0]*2 );
Console.WriteLine( ar[0] );
Console.WriteLine( ar[1] );
于 2011-10-24T09:31:17.593 回答
1

这对于 OO 语言来说是完美的,因为引用的工作方式。在抽象层面上,我会这样处理:

创建一个抽象类 ,Expression它将成为我们程序中所有值的基类型。就像是:

public abstract class Expression
{
    List<Expression> linkedExpressions;
    protected Expression lhs; // left hand side, right hand side
    protected Expression rhs;

    protected Expression(Expression x, Expression y)
    {
        List<Expression> linkedExpressions = new List<Expression>();
        lhs = x;
        rhs = y;
        // let the expressions know that they have a expression dependant on them
        lhs.NotifyExpressionLinked(this); 
        rhs.NotifyExpressionLinked(this);
    }

    private void NotifyExpressionLinked(Expression e)
    {
        if (e != null)
        {
            linkedExpressions.Add(e);
        }
    }

    private void NotifyExpressionUnlinked(Expression e)
    {
        if (linkedExpressions.Contains(e)
        {
            linkedExpressions.Remove(e);
        }
    }

    // this method will notify all subscribed expressions that
    // one of the values they are dependant on has changed
    private void NotifyExpressionChanged()
    {
        if (linkedExpressions.Count != 0) // if we're not a leaf node
        {
            foreach (Expression e in linkedExpressions)
            {
                e.NotifyExpressionChanged();
            }
        }
        else Evaluate() 
        // once we're at a point where there are no dependant expressions 
        // to notify we can start evaluating
    }

    // if we only want to update the lhs, y will be null, and vice versa
    public sealed void UpdateValues(Expression x, Expression y)
    {
        if (x != null)
        {
            lhs.NotifyExpressionUnlinked(this);
            x.NotifyExpressionLinked(this);
            lhs = x;
        }

        if (y != null)
        {
            rhs.NotifyExpressionUnlinked(this);
            y.NotifyExpressionLinked(this);
            rhs = y;
        }

        NotifyExpressionChanged();
    }

    public virtual float Evaluate()
    {
        throw new NotImplementedException(); // we expect child classes to implement this
    }
}

为我们需要的每种表达式类型创建一个类。在最底部,您将有一个LiteralExpression,它只是一个数字:

public class LiteralExpression : Expression
{
    private float value;

    public LiteralExpression(float x)
        : base(null, null) { } // should not have any linked expressions

    public override float Evaluate()
    {
        return value;
    }
}

关于这个类需要注意的一点 - 由于它的工作方式,不应在其上使用 UpdateValue()。相反,只需创建一个新的 LiteralExpression 来替换它。

然后,您需要为您想要的所有表达式构建子类(例如,这里是添加):

public class AdditionExpression : Expression
{
    public AdditionExpression(Expression x, Expression y)
        : base(x, y) { };

    public override float Evaluate()
    {
        return lhs.Evaluate() + rhs.Evaluate();
    }
}

不是我们必须为每个表达式编写的所有代码——所有繁重的工作都由抽象类处理。这个程序有一些设计缺陷——它不会检测循环引用,也不会阻止你将null值传递给表达式(在 LiteralExpression 的情况下是必需的),但这些问题应该不会太难修理。

然后,需要做的就是实现所有从表达式继承的子类。

可能有更好的方法来做到这一点,但从 OO 的角度来看,这是一个很好的例子,它创建了一个实现常见行为的通用抽象类,并为该类的每个“风格”提供了许多小的、特定的实现。

于 2011-10-24T10:38:01.263 回答