Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CallMemberName – An Easier Way To Do INotifyPropertyChanged and More

0.00/5 (No votes)
27 Aug 2012 1  
An easier way to do INotifyPropertyChanged

In WPF, when applying the MVVM (an architectural pattern), we often need to implement the INotifyPropertyChanged on certain classes (ViewModel classes), which means something like this:

public class PersonViewModel : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name == value) return;
            _name = value;
            NotifyPropertyChanged("Name");
        }
    }

    private void NotifyPropertyChanged(string propertyName)
    {
        var evt = PropertyChanged;
        if (evt != null) evt(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

In case you’re wondering why I copied the PropertyChanged value to the local variable called “evt” and then tested it for null is that you can have race conditions, in general, triggering events (i.e.: you test the attribute value, it is not null and before you trigger it, some other thread sets it to null and bang, NullReferenceException when you trigger it). More details on this CodeProject.

The next step is to pull the NotifyPropertyChanged method and PropertyChanged event into a base class (let’s call it ViewModelBase) and you’ve eliminated redundancy between several ViewModel classes.

The not-so-nice part is having the call to NotifyPropertyChanged stringly-typed. That means that if later you rename (via Visual Studio or ReSharper) the Name property to “FullName” the call will still pass “Name” as the argument.

Some blog posts around the web show how you can use a Func to make it type-safe (refactor safe, etc.).

More or less they’re doing the same thing:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(Expression<Func<object>> propertyAccessor)
    {
        var evt = PropertyChanged;
        if (evt == null) return;
        var propertyName = propertyAccessor.GetName();
        evt(this, new PropertyChangedEventArgs(propertyName));
    }
}

public static class Utils
{
    public static string GetName(this LambdaExpression expression)
    {
        MemberExpression memberExpression;
        if (expression.Body is UnaryExpression)
        {
            var unaryExpression = (UnaryExpression)expression.Body;
            memberExpression = (MemberExpression)unaryExpression.Operand;
        }
        else if (expression.Body is MemberExpression)
        {
            memberExpression = (MemberExpression)expression.Body;
        }
        else
        {
            return null;
        }
        return memberExpression.Member.Name;
    }
}

This is definitely nicer, not-redundant and type-safe. It does have the drawback of having some runtime performance penalty associated with the reflection of the expression. You could cache the property name string in a private field but then you’d have to write more code in the ViewModel classes which would… suck. In practice, this performance penalty is negligible so you can just ignore this.

Then came .NET 4.5 and among other improvements a new mechanism has been introduced: CallMemberName.

Historically, some folks tried to get programmatically the name of the caller method by inspecting the StackTrace (for example, using System.Environment.StackTrace) but this is prone to errors since in Release mode, the compiler could eliminate some methods by inlining them and you’ll be screwed. Plus the penalty would be higher than reflecting an expression.

The new mechanism in .NET 4.5 is type-safe, has no runtime performance penalty and it’s more elegant. Here’s how you can use it:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var deleg = PropertyChanged;
        if (deleg != null)
        {
            deleg(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class PersonViewModel : ViewModelBase
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name == value) return;
            _name = value;
            NotifyPropertyChanged();
        }
    }
}

I’ve recently built a very small GuidGen utility (which as the name implies generates GUIDs, copies it in the Windows Clipboard and stores a history of past generated GUIDs). You can browse some of the code and check out the project.

Much nicer, isn’t it?

Funny thing, this new mechanism can be used for non-UI tasks. For example, if you have a project that uses an RDBMS and you use stored procedures. Let’s say you have one method in a repository class for each stored procedure, and even more, the method’s name matches the stored procedure’s name:

public VerificationResult VerifyUser(VerificationData verificationData)
{
    if (EmailValidator.IsEmailInvalid(verificationData.EmailAddress))
        throw new FormatException("emailAddress");

    var result = CreateNewCommand("VerifyUser").GetEnumResult<VerificationFailReason>(
        CreateEmailAddressParameter(verificationData.EmailAddress),
        CreateUniqueIdentifierParam("@VerificationCode", verificationData.VerificationCode));

    return new VerificationResult(result);
}

Observe on line 5 how the call to CreateNewCommand passes a string which matches the current method’s name. This can also be simplified (and become refactor-safe) using the new CallMemberName mechanism.

So you can’t really say that CallMemberName is useful only for UI tasks. :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here