Is there a good strongly typed way to do PropertyChanged events in C#?
Asked Answered
S

7

12

It must be a somewhat common event to change the name of a property and expect the Rename functionality in Visual Studio to take care of all the necessary renaming, except for the property name of the PropertyChanged event of INotifyPropertyChanged. Is there a better way to somehow get it strongly typed so you don't need to remember to manually rename it?

Secretive answered 14/7, 2009 at 21:16 Comment(3)
(example added as per request/comment)Omarr
Take a look at this article. weblogs.asp.net/dwahlin/archive/2009/07/07/… If you create a base class that implements INotifyPropertyChanged and then derive from it, maybe you can use this.Keir
see #1329638 for a compiler checked way of implementing INotifyPropertyChanged. Avoiding having the property names as a magic string.Raber
O
8

Edit: nameof arrived in c# 6. Yay!


There is no nameof / infoof etc; this is much discussed, but it is what it is.

There is a way to do it using lambda expressions in .NET 3.5 (and parsing the expression tree), but in reality it isn't worth the overhead. For now, I'd just stick with strings (and unit tests if you are determined not to break it).


using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
class Program : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    static void Main() {
        var p = new Program();
        p.PropertyChanged += (s, a) => Console.WriteLine(a.PropertyName);
        p.Name = "abc";
    }
    protected void OnPropertyChanged<T>(Expression<Func<Program, T>> property) {
        MemberExpression me = property.Body as MemberExpression;
        if (me == null || me.Expression != property.Parameters[0]
              || me.Member.MemberType != MemberTypes.Property) {
            throw new InvalidOperationException(
                "Now tell me about the property");
        }
        var handler = PropertyChanged;
        if (handler != null) handler(this,
          new PropertyChangedEventArgs(me.Member.Name));
    }
    string name;
    public string Name {
        get{return name;}
        set {
            name = value;
            OnPropertyChanged(p=>p.Name);
        }
    }
}
Omarr answered 14/7, 2009 at 21:23 Comment(5)
I'll probably just stick to the strings, but for kicks, could you give a short code sample on how to parse the expression tree?Secretive
Why do you use Expression<Func<Program, T>> and p=>p.Name instead of Expression<Func<T>> and ()=>Name?Wampler
Either would be fine, but the version as posted makes it clear that we are looking for a property on the instance - not just something random.Omarr
Specifying Expression<Func<Program, T>> on your base view-model class won't allow subclasses to raise it's properties.That's why Expression<Func<T>> is better for that purpose.Sacerdotal
No longer true! Now you can use nameof.Overlook
C
6

C# 5 seem to have a solution. With an CallerMemberName attribute that can be used with parameters (One example on the net).

class Employee : INotifyPropertyChanged
{
    private string _Name;
    public string Name
    {
        get { return _Name; }

        set
        {
            _Name = value;
            RaisePropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged([CallerMemberName] string caller = "")
    {
        var temp = PropertyChanged;

        if ( temp != null )
        {
            temp( this, new PropertyChangedEventArgs( caller ) );
        }
    }
}
Consequence answered 17/1, 2013 at 8:44 Comment(0)
T
2

The simplest solution is to look at the stack trace and completly remove every explicit reference to the property.

public String Name
{
    get { return this.name; }
    set
    {
        if (value != this.name)
        {
            this.RaisePropertyChanging();
            this.name = value;
            this.RaisePropertyChanged();
        }
    }
}
private String name = null;

private void RaisePropertyChanged()
{
    String propertyName =
       new StackTrace().GetFrame(1).GetMethod().Name.SubString(4);

    PropertyChangedEventHandler handler = this.PropertyChanged;
    if (handler != null)
    {
        handler(new PropertyChangedEventArgs(propertyName));
    }
}

The code derives the property name through the stack trace from the caling method - that is the property setter method named set_<PropertyName>. If the compiler no longer follows this naming convention, the code breaks.

The other solution is to derive the property name from a lambda expression.

public static String GetPropertyNameFromLambdaExpression<TObject, TProperty>(
    Expression<Func<TObject, TProperty>> expression)
{
    return ((MemberExpression)expression.Body).Member.Name;
}

For example

GetPropertyNameFromLambdaExpression<String, Int32>(s => s.Length)

will return "Length" as exspected. A production version of the code really demands additional checks and better integration into the rest of the code. For example it is possible to use type inference for the generic arguments.

UPDATE

And there is a third solution - you can use MethodBase.GetCurrentMethod() inside a property getter or setter to obtain the name of the setter or getter method.

public String Name
{
    get { return this.name; }
    set
    {
        if (value != this.name)
        {
            String propertyName = MethodBase.GetCurentMethod().Name.SubString(4);

            this.RaisePropertyChanging(propertyName);
            this.name = value;
            this.RaisePropertyChanged(propertyName);
        }
    }
}
private String name = null;
Tribal answered 14/7, 2009 at 21:46 Comment(3)
This solution is more fragile than simply setting the property name as a string.Ergonomics
This looks interesting. Without checking the documentation, though, I can't quite figure out what the call to SubString is for. Does GetMethod().Name return something strange?Grate
A property is implemented by two methods. public MyType MyProperty { get; set; } is implemented as public void set_MyProperty(MyType value) { } and public MyType get_MyProperty(). Hence you have to remove set_ and get_ from the returned method name to get the name of the property.Abound
A
1

In theory, you could use MethodBase.GetCurrentMethod().Name.Substring(4) from within the property setter. Unfortunately, Google search reveals that it seems to have a significant performance impact. Two more things to consider:

  • JIT inlining can impact this in unexpected ways. (https://mcmap.net/q/480189/-can-i-check-if-the-c-compiler-inlined-a-method-call)
  • In theory, the IL call to MethodBase.GetCurrentMethod() could be trivially replaced by the JIT at runtime with a ldtoken instruction followed by a call to MethodBase.GetMethodFromHandle(), which would be very fast. I guess users just haven't expressed a need for this. (msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldtoken.aspx)
  • Completely my opinion here, but I think it'd be nice to have fieldof() and methodof() operators in C#. I believe it would greatly improve the reliability of the code analysis/refactoring tools in projects that require that ability.
Abracadabra answered 14/7, 2009 at 22:17 Comment(0)
U
0

Not an answer to your question, but if you right-click->Refactor->Rename a property, it can rename matching strings as well, including any strings that match your property's name.

Yeah, it can be a bit dangerous.

Unflinching answered 14/7, 2009 at 21:22 Comment(1)
Even rename in comments is dangerous. I messed up a bunch of XML documentation in a rather large project by assuming that this feature would limit scope to comments on/in the code element being renamed.Cog
L
0

You should check out this blog post. It gives you the ability to do this:

string propertyName = TypeHelper.GetPropertyName<User>(u => u.LastProjectCode);

PropertyInfo property1 = TypeHelper.GetProperty((SomeClass o) => o.InstanceProperty.Length);

PropertyInfo property2 = TypeHelper.GetProperty(() => SomeClass.StaticProperty.Length);

Renames in Visual Studio/Resharper/Refactor Pro should work for you then.

Lofty answered 2/11, 2009 at 17:23 Comment(0)
E
-1

The PropertyChangedEventArgs only takes one constructor, which requires the property name as a string. So essentially no- utilizing INotifyPropertyChanged means that at some level, be it high or low in your architecture, you will have to work with a string and manual renaming.

Ergonomics answered 14/7, 2009 at 21:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.