Gemakkelijk INotifyPropertyChanged implementeren met Generics
379

Het is een veelvuldig onderschatte feature en vaak wordt er eenvoudig overheen gekeken.
Het INotifyPropertyChanged event. Bruikbaar? Jazeker. Hiermee zou je zo maar ineens property history kunnen toevoegen, en daarmee undo en redo. Logging kan dan ook op propertyniveau.
Het probleem zit hem vaak in implementatie. Op MSDN staat de veelgebruikte implementatie beschreven waarin je in elke property af moet vangen of de NotifyPropertyChanged methode moet worden aangeroepen. Omslachtig en in veel gevallen niet praktisch. Zou het niet handiger zijn om dat op een andere, generiekere manier te doen? Ja. En ik heb er twee voor je.

Bindable

De Bindable class implementeert de INotifyPropertyChanged interface.
Er zijn drie methodes waarin deze wordt aangeroepen.

T Get([CallerMemberName] string name = null)
void Set(T value, [CallerMemberName] string name = null)
void OnPropertyChanged([CallerMemberName]string property = null)

Middels een property collectie worden waardes opgeslagen en opgehaald.
In de Set methode wordt de OnPropertyChanged aangeroepen.

Door deze class als basis voor je data- en/of businessmodels te pakken kan je daar op eenvoudige wijze deze methodes gebruiken.

public string Name
{
get
{
return Get();
}
set
{
Set(value);
}
}

En dat is alles! Vanaf nu heb je propertyhistory en een mooie implementatie van INotifyPropertyChanged.

protected internal void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyHistory[e.PropertyName].Add(this);
State = State.Modified;

PropertyChanged?.Invoke(this, e);
}

PropertyHistory is een custom class die ICollection implementeert met PropertyValue, een andere custom class. Gebruik deze methode als je je bestaande code wilt uitbreiden met INotifyPropertyChanged.

Concept

Deze is wat complexer. Opnieuw een class die een interface implementeert maar deze op een iets andere wijze gebruikt. Het idee achter Concept is dat je door middel van een static constructor je types aanmaakt, of eigenlijk construeert.

public static class Create where TConcept : Concept
{
//Construct derived Type
public static readonly Type Type =
PropertyProxy.ConstructType<TConcept, Implementation>(new Type[0], true);

//Create constructing delegate
public static Func New = Constructor.Compile<Func>(Type);
}

Deze Create functionaliteit maakt dynamische instanties aan en voegt daar het PropertyChanged event aan toe. Zonder op al te veel detail in te gaan (bron code op GitHub), resulteert dat in classes waarbij elke VIRTUAL property automatisch een PropertyChangedEvent krijgt.

Dus in plaats van

public string Name{get;set;}

doe je

public virtual string Name{get;set;}

En dat is alles.
Classes kan implementeren met de Create functie.

car = Concept.Create.New();

De code beslaat heel wat meer dan bovenstaand stukje code doet vermoeden. Je ziet in de Create functionaliteit al iets, maar er is een hele reflection architectuur nodig om dit te doen.
Het resultaat mag er echter wel zijn doordat je een feilloze implementatie hebt van INotifyPropertyChanged zonder dat je daar eigenlijk in de classes iets voor hoeft te doen. Zolang je prop maar virtual is, werkt het.

Vooral bij nieuwe projecten zou ik deze methode aanbevelen.

Conclusie

Het hoeft niet moeilijk te zijn om op eenvoudige wijze een property collectie aan te maken en op deze manier een property history bij te houden. De INotifyPropertyChanged interface is er één die al lang in het .Net Framework zit, maar eigenlijk zelden wordt toegepast doordat implementatie ervan log en moeizaam is. Dit artikel probeerde je te laten zien dat dat niet zo hoeft te zijn en dat je dat stukje extra functionaliteit eenvoudig kan inschuiven in bijv. je MVVM context.

Auteur: Arjan Crielaard, Bergler Competence Center 2016.

Broncode