【问题标题】:MVVM update of calculated properties计算属性的 MVVM 更新
【发布时间】:2013-10-25 13:52:21
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 我不知道这里有“正确/错误”的答案。此外,ViewModel 中的封装是否存在很大问题?无论您如何处理,VM 仍处于封装状态,并且从 View 的角度来看是一个“黑匣子”。
  • 我对封装的担忧是对可扩展性的担忧。我真的在考虑具有复杂 UI 和多个 ViewModel 的情况。

标签: c# wpf mvvm


【解决方案1】:

这里最常提供的解决方案似乎是获取所有 税值依赖于调用 PropertyChanged 的​​属性 属性本身和每个属性的 ModelView 取决于它。

不,支持属性不需要自己的更改通知,除非它们正在显示。但是每个属性都需要直接在其设置器中调用税值的OnPropertyChanged("TaxValue");或间接根据下面的示例。由于 supporting 属性已更改,因此 UI 会更新。

话虽如此,让我们考虑一个例子。一种方法是创建一种方法来计算值。当设置了最终值(下面的 TaxValue)时,它将调用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")

【讨论】:

  • +1:也许值得一提:通过更新,您可以在 .net 4.0/Vs 2010 中使用 .net 4.5 OnPropertyChanged 属性名称推断。它不是 4.0 开箱即用的。
  • @jdv-JandeVaan 我的错...我编辑了帖子以反映如何在 .Net 4 中获取它。谢谢!
  • +1 好答案。我会添加类声明以显示 INPC 的继承,但除此之外是经典的 MVVM。
  • 这似乎没有帮助。如果 FigureTax 依赖于某个新属性,则该属性的设置器的代码仍需要更改,因此它现在调用 FigureTax。如果它们都在我的控制之下,我可以做到这一点(尽管如果我错过了一个等待发生的麻烦——我过去 20 年的编程经验一直在学习使这些事情变得不可能的方法!)在一个复杂的情况下,虽然,可能有多个 ViewModel 处理 UI 的不同部分,因此我可能无法更改设置器。
  • @GarryVass 很好的建议。完成。
【解决方案2】:

查看 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 + "!");

它的大小令人难以置信的强大:想想 View Model 属性的类似 Excel 的公式引擎。

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

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

【讨论】:

    【解决方案3】:

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

    是的,但仅限于该对象:每个属性都应在 setter 中触发其自己的属性更改事件。此外,setter 应该以某种方式触发依赖于自身值的属性。你不应该尝试主动触发其他对象的更新:他们应该监听这个对象PropertyChanged

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

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

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

    【讨论】:

    • 好的,我可以看到,在复杂的情况下,处理程序最终可能会在可能更改的属性上使用一个大的 switch 语句,在它们可能影响的每个属性上调用 PropertyChanged。这听起来像是一个维护噩梦,即使它不是一场噩梦。我想这个问题的答案是将具有复杂计算的属性重构到它自己的类中,因此 PropertyChanged 处理程序只需要处理一个属性,而不是包含类可能具有的(可能)许多计算属性。
    【解决方案4】:

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

    您可以使用 ILSpy 反编译已编译的代码,以查看它做了什么并验证它是否引发了所有适当的事件。

    【讨论】:

    • 有趣——看来我不是唯一一个有这个问题的人,然后,一个解决方案是可能的。
    • Fody、PostSharp 不支持在运行时已知或更改的依赖关系图(例如,Total 是子视图模型小计列表的总和)。 github.com/StephenCleary/CalculatedProperties 但是不需要额外的样板就可以做到这一点
    【解决方案5】:

    我能想到的最好的解决方案是让类 计算 receive PropertyChanged 事件,并引发一个新的 税值发生任何变化时的 PropertyChanged 事件 进入计算。这至少保留了封装 class 级别,但它仍然违反 method 封装:类不必知道方法如何工作。

    我认为您将“封装”一词扩展到对语法的争论。这里没有问题,例如:

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

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

    同样,在类初始化中为每个属性设置事件处理程序也没有问题。只要它在初始化时只出现一次,并且不需要“知道”那些特定的处理程序被添加,你的属性在逻辑上仍然是独立的。您可能会以某种方式在属性上使用属性,例如[DependsOn(property1, property2)],但这只是代码可读性问题。

    【讨论】:

    • 我不认为这是一个特别接近的类比。我对避免错误的务实问题比对封装纯度的教义问题更感兴趣。 _methodXCalls 的其他用户——它所属类的维护者——不关心 MethodX 如何保持更新,只关心它。如果他们必须改变 MethodX,他们不需要知道还有什么用它。更改不必跨越边界传播。
    • @digitig 非常接近;事实上,它甚至不是一个“类比”,而只是同一概念的另一个例子:初始化。唯一的区别是字段可以在类级别初始化,而事件订阅必须放在构造函数中。这主要是一个语法限制。在这两种情况下都没有“越界”:属性Tax 的其他用户依赖于不关心事件订阅,而Tax 本身及其初始化并不关心更新属性的内容,仅此而已他们更新。问题出在哪里?
    • 问题是我在几年后维护代码时可能会犯错误的感觉。我很可能会搞砸复杂依赖项的更新,但我只会在特别繁重的通宵之后搞砸_methodXCalls的使用。
    猜你喜欢
    • 2020-09-13
    • 2019-06-18
    • 2019-04-18
    • 1970-01-01
    • 1970-01-01
    • 2020-08-11
    • 2014-03-01
    • 2019-10-03
    相关资源
    最近更新 更多