计算属性的 MVVM 更新

本文关键字:更新 MVVM 属性 计算 | 更新日期: 2023-09-27 18:36:56

我只是在学习 MVVM,我正在尝试如何显示由于计算值的更改而对计算属性的更改。到目前为止,我看到的所有解决方案都严重违反了封装,我想知道是否有更好的方法。

假设我需要显示的一件事是复杂的税收计算的结果。计算(可能还有它的依赖项)会不时更改,因此我想严格封装它。

这里最常提供的解决方案似乎是获取税收值所依赖的所有属性,以便在模型视图中为属性本身以及依赖于它的每个属性调用 PropertyChanged。这意味着每个属性都需要知道使用或可能使用它的所有内容。当我的税收规则发生变化,使计算依赖于以前不依赖的东西时,我将需要接触进入我的计算的所有新属性(可能在其他类中,可能不受我的控制),以使它们调用 PropertyChanged 作为税收值。这完全破坏了封装的任何希望。

我能想到的最佳解决方案是使执行计算的类接收 PropertyChanged 事件,并在计算中的任何更改时为税值引发一个新的 PropertyChanged 事件。这至少保留了类级别的封装,但它仍然违反了方法封装:不应该知道方法是如何工作的。

所以,我的问题是,有没有更好的方法(如果有,它是什么)?还是表示封装 (MVVM) 阻止了业务逻辑的封装?我是否面临非此即彼的选择?

计算属性的 MVVM 更新

这里最常提供的解决方案似乎是获得所有 税值所依赖的属性调用属性更改于 属性本身和每个属性的模型视图 取决于它。

否 除非显示支持属性,否则不需要其自己的更改通知。但是,每个属性都需要直接在其二传手中调用税值的OnPropertyChanged("TaxValue");或间接按照以下示例。这样,UI 就会更新,因为支持属性已更改。

话虽如此,让我们考虑一个例子。一种方法是创建一个将进行值计算的方法。当设置了最终值(下面的税值)时,它将调用OnNotifyPropertyChange。该操作将通知全世界用户 TaxValue 更改;不管什么值触发它(扣除|费率|收入):

public class MainpageVM : INotifyPropertyChanged 
{
       public decimal TaxValue 
        {
           get { return _taxValue; }
           set { _taxValue = value; OnPropertyChanged(); }  // Using .Net 4.5 caller member method.
        }
        public decimal Deduction
        {
           get { return _deduction; }
           set { _deduction = value; FigureTax(); }
        }
        public decimal Rate
        {
           get { return _rate; }
           set { _rate = value; FigureTax(); }
        }
        public decimal Income
        {
           get { return _income; }
           set { _income = value; FigureTax(); }
        }
        // Something has changed figure the tax and update the user.
        private void FigureTax()
        {
           TaxValue = (Income - Deduction) * Rate;
        }

    #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>Raises the PropertyChanged event.</summary>
        /// <param name="propertyName">The name of the property that has changed, only needed
        /// if called from a different source.</param>
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    #endif
    }

编辑

若要在 .Net 4 中使用 CallerMemberName(和其他项),请安装 Nuget 包:

Microsoft.BCL.

或者,如果不,请改用标准OnPropetyChanged("TaxValue")

查看 Stephen Cleary 的计算属性: https://github.com/StephenCleary/CalculatedProperties

它非常简单,就是这样做的:传播依赖属性的通知,而不会污染触发器属性设置器。

原始示例:

public string Name 
{
  get { return Property.Get(string.Empty); }
  set { Property.Set(value); }
} 
public string Greeting => Property.Calculated(() => "Hello, " + Name + "!");

它的大小非常强大:想想视图模型属性的类似 Excel 的公式引擎。

我在域和视图模型类的几个项目中都使用了它,它帮助我消除了大部分命令式控制流(错误的主要来源),并使代码更具声明性和清晰度。

最好的一点是,依赖属性可以属于不同的视图模型,并且依赖关系图在运行时可能会发生巨大变化,并且它仍然可以正常工作。

这里最常提供的解决方案似乎是获取税收值所依赖的所有属性,以便在模型视图中为属性本身以及依赖于它的每个属性调用 PropertyChanged。

是的,但仅适用于该对象:每个属性都应在资源库中触发自己的属性更改事件。此外,资源库应以某种方式触发依赖于自身该值的属性。您不应尝试主动触发其他对象的更新:它们应侦听此对象PropertyChanged

我能想到的最佳解决方案是使执行计算的类接收 PropertyChanged 事件,并在计算中的任何更改时为税值引发一个新的 PropertyChanged 事件。这至少保留了类级别的封装,但它仍然违反了方法封装:类不应该知道方法是如何工作的。

这确实是标准方法。每个类都负责监视它所依赖的属性,并触发它所依赖的属性的属性更改事件。

可能有一些框架可以帮助您做到这一点,但知道应该发生什么是值得的。

有一个名为 Fody/PropertyChanged 的插件,它在编译时工作以自动实现PropertyChanged。它将自动查看同一类中的哪些属性使用了您的属性,并在一个复杂的税收计算发生变化时引发所有适当的PropertyChanged事件。

您可以使用 ILSpy 反编译已编译的代码,以查看它执行了哪些操作,并验证它是否引发了所有适当的事件。

我能想到的最好的解决方案是使执行 计算接收属性更改事件,并引发新的 属性更改了税值的事件,当任何更改时 进入计算。这至少保留了封装 类级别,但它仍然违反了方法封装:不应该知道方法是如何工作的。

我认为您正在将术语"封装"扩展到对语法的争论。这里没有问题,例如:

private int _methodXCalls;
public void MethodX() {
    Console.WriteLine("MethodX called {0} times", ++_methodXCalls);
}

该字段仅在 MethodX 中相关,但仅仅因为声明在语法上不在 MethodX 并不意味着它破坏了方法封装。

同样,在类初始化中为每个属性设置事件处理程序也没有问题。只要它在初始化时只出现一次,并且不需要任何其他东西来"知道"这些特定的处理程序已被添加,您的属性在逻辑上仍然是自包含的。您可能可以以某种方式在属性上使用属性,例如 [DependsOn(property1, property2)],但这实际上只是一个代码可读性问题。