Using lambda to identify property name

2019-09-06 07:26发布

问题:

I have a question regarding the following code:

public class MyClass : INotifyPropertyChanged
{
    private bool _myProp;
    public bool MyProp
    {
        get { return _myProp; }
        set
        {
            _myProp = value;                
            RaisePropertyChanged(() => MyProp);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

This may not be the best way to identify a property name, but I have used it before, even in the same project; however, the above code won't compile. There's several workarounds for this; some of which may be better solutions that what is above, however, I'd still like to find out why this doesn't work.

The specific compile error I get is:

error CS1660: Cannot convert lambda expression to type 'string' because it is not a delegate type

回答1:

You need a method that accepts Expression<Func<T>>, extracts the property name as a string, and then raises the PropertyChanged event with it. It won't be done automatically. I usually make it an extension method, to save implementing the same code over and over or having it in a base class:

public static class RaisePropertyChangedExtensions
{
    public static void RaisePropertyChanged<T>(
        this IRaisePropertyChanged raisePropertyChangedImpl,
        Expression<Func<T>> expr)
    {
        var memberExprBody = expr.Body as MemberExpression;
        string property = memberExprBody.Member.Name;
        raisePropertyChangedImpl.RaisePropertyChanged(property);
    }
}

Your view-models just need to implement the IRaisePropertyChanged interface:

public interface IRaisePropertyChanged : INotifyPropertyChanged
{
    void RaisePropertyChanged(string property);
}

..and the usage is exactly the same as in your question:

this.RaisePropertyChanged(() => MyProp);

Of course, you can always make this a method on your view-model - just remove the generic parameter and pass your view-model type to the function.



回答2:

Checkout the new CallerMemberName attribute. I only found out about it via mvvm light but will never do notify property changed the old way again.

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx



回答3:

You need to use expressions:

public static string GetPropertyName<T, TPropValue>(this Expression<Func<T, TPropValue>> propertySelector) where T : class
{
    Condition.Requires(propertySelector, "propertySelector").IsNotNull();

    var memberExpr = propertySelector.Body as MemberExpression;
    if (memberExpr == null)
         throw new ArgumentException("Provider selector is not property selector.");

    var propInfo = memberExpr.Member as PropertyInfo;       
    if (propInfo == null)
         throw new NotSupportedException("You can properties only.");

    return propInfo.Name;
}

protected void RaisePropertyChanged(Expression<Func<MyClass, string>> propSelector) 
{  
   if (PropertyChanged != null)
   {
       var propertyName = propertySelecotr.GetPropertyName();
       PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
}

Usage:

RaisePropertyChanged(myClass => myClass.MyProp);