MVVM Model Property Setter

Introduction

Click here to view the source code

Working with MVVM tends to require quite a bit of boiler plate code to set up and use. Many developers will encapsulate common functionality to allow for better visibility and maintainability throughout the codebase. One repetitive functionality that occurs within ViewModels is setting properties. Although hiding the business logic for setting a property can be done on a private member, attempting to do the same on the Model is not quite as clean. In this article I will provide a solution to help reduce boilerplate code for setting a property using a Model’s property without losing property changed events.

Encapsulating Property Setter

First, let us view how setting a ViewModel’s property can be encapsulated. Using the CallerMemberName attribute for receiving the caller’s property name, an implementation can be made to generically set the incoming value along with firing off a PropertyChanged event:

protected bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
	if (Equals(storage, value) || string.IsNullOrEmpty(propertyName))
	{
		return false;
	}

	storage = value;
	OnPropertyChanged(propertyName);
	return true;
}

By doing this, setting the property is quite straight forward with just a few lines:

private string _name;
public string Name
{
	get => _name;
	set => SetProperty(
			ref _name,
			value);            
}

Problems occur though when you want to follow the same pattern using a Model object’s property rather than the ViewModel’s private member. The reason a Model’s property cannot be used for our current SetProperty implementation has to do with the ref keyword since arguments must either be a variable, field, or array. Because of this, the Model’s property must be set to a temp variable to help with the transfer.

public string Name
{
	get => _toDo.Name;
	set
	{
		var name = _toDo.Name;
		if (SetProperty(
			ref name,
			value))
		{
			_toDo.Name = name;
		}
	}
}

This does not look pleasing to the eyes and may even provide enough of a reason to ignore SetProperty altogether. But if all the other public properties use SetProperty it provides a divide in how setting the property and firing the property changed events occur. Given that the current method is not acceptable let us try to rewrite the method to provide more flexibility

Property Setter with the Model

Since the ref keyword cannot be used to pass in the Model’s property, the only other solution is to leverage reflection. In order to do this, the Model’s class type, incoming value type, and the Model’s property as a form of expression is needed to reflect down to the PropertyInfo and set the Model’s property. The expression provides much of the metadata needed to retrieve the Model’s property current value, but the GetValue and SetValue methods are where the majority of reflection takes place. The solution looks similar to this:

protected bool SetProperty<TClassType, TValueType>(
TClassType classObj, 
Expression<Func<TClassType, TValueType>> outExpr, 
TValueType value, 
[CallerMemberName] string propertyName = null)
{
        var exprBody = outExpr.Body;
	if (exprBody is UnaryExpression)
	{
		exprBody = ((UnaryExpression)outExpr.Body).Operand;
	}

	var expr = (MemberExpression)exprBody;
	var prop = (PropertyInfo)expr.Member;
	var refValue = prop.GetValue(classObj, null);

	if (Equals(value, refValue))
	{
		return false;
	}
	prop.SetValue(classObj, value, null);
	OnPropertyChanged(propertyName);
}

By adding in this method, our ViewModel’s property setter is reduced to this:

public string Name
{
	get => _toDo.Name;
	set => SetProperty(
		_toDo,
		t => t.Name,
		value);
}

Now the ViewModel’s property setters are much easier to maintain and view.

Conclusion

Although this solution is elegant, the reflection aspect makes the function more of a convenience rather than a practical usage for a very highly performant application. Compared to the two styles of property setters, the new SetProperty is at least 6% slower. The speed costs may be negligible based on the needs of your app, but it is certainly something to keep in mind.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s