Sergio giving love to some C# code

MVVM Base in C#

Implementing the bases

Sergio Morchón Poveda
Making Tuenti
Published in
3 min readMay 4, 2017

--

Before you begin

MVVM is a design pattern (Model-View-ViewModel) within the MVW (Model-View-Whatever) category. It is especially useful when you want to separate the UI code (view) from the data (models), while at the same time having everything connected and synchronised (bindings).

A handmade ViewModel

Let’s develop a simple example to model a person with first name, last name and full name:

Plain Person class

By implementing ​INotifyPropertyChanged​, we can notify changes in the properties of our objects:

Observable Person class

Now for a couple of questions:

  • What happens if we rename the FirstName property?
    We will also need to manually change the “FirstName” string within the PropertyChanged invocation. It is not refactorable. But there is a solution: ​nameof​ or, as outlined below, CallerMemberNameAttribute​.
  • What happens when we have computed properties, such as FullName, combining FirstName and LastName?
    We will need to notify the change of the property itself and of FullName in each setter (FirstName and LastName). If not, the name or last name could change and nobody would know that the full name has also changed.
Observable and refactorizable Person class

Implementing concepts

So far, everything works. But things get increasingly complicated as we add computed properties (for example, for Weight and Height, BodyMassIndex would also be computed). In order to reuse code, make it less verbose and improve maintenance (of computed properties, above all), we will implement two classes: ​BindableBase​ and ComputedBindableBase​.

Bindable base class

Our ViewModels will inherit from this class, so the INotifyPropertyChanged interface will already be implemented. To reduce verbosity we are using the Set method.
The purpose of its arguments is as follows:

  • ref​ ​T​ target​: property of the class where the value is stored, passed by reference. It will usually be private and will be associated with a more visible getter property.
    Why use a parameter by reference? Because it enables us to encapsulate the assignment of the new value and also the notification that the property has changed.
  • T​ value​: the new value of the property (pretty obvious).
  • [​CallerMemberName​] ​string​ propertyName = ​””​: This is where there is more substance. ​CallerMemberName​ tells the compiler services to insert the name of the caller (the function that called it) and assign it to propertyName. In turn, to make it optional, propertyName defaults to an empty string.

Bringing all this together, we have to call the Set method from, for example, the FirstName setter; it will store the new value and launch the event with the name “FirstName” (caller) for us.

Computed and observable properties

So far so good, but if you change the FirstName or LastName, we still have notify that the FullName has also changed. In order to do this, we are going to use an attribute that we will put on the computed property (declarative), instead of launching events from the “source” properties (imperative).

We will achieve this with an attribute and a little bit of reflection.

PropertySource attribute class

This attribute will be placed on the computed properties, passing the names of the properties on which it depends as arguments.

ComputedBindableBase class

This class inspects the properties of the instance and, if it finds the attribute we just created, adds the dependency to a dictionary. By the time the constructor is finished, a handler will be added to PropertyChanged that will launch the events required for the computed properties.

A manageable ViewModel

Person class

Less code, more expressive and refactorable.

You can see a working example in a unit test just here.

Can I use this?

Of course :)
You have at your disposal:

It is implemented using .NET Standard 1.4 and VisualStudio Code, so you can play around with it on any supported platform (i.e. Linux, Mac, Windows).

--

--