C# 我应该创建很多PropertyChangedEventHandler还是测试PropertyChangedEventArgs?

C# 我应该创建很多PropertyChangedEventHandler还是测试PropertyChangedEventArgs?,c#,wpf,properties,event-handling,inotifypropertychanged,C#,Wpf,Properties,Event Handling,Inotifypropertychanged,到目前为止,我的模型实现了INotifyPropertyChanged,每个属性都会引发此事件。 几乎所有的视图模型都通过属性changedeventhandler监听这些更改 问题是,即使属性更改对视图不重要,也会为模型中的每个更改调用此处理程序 一个选项是检查引发事件的属性。但是,我不喜欢测试PropertyName字符串的想法。它需要对属性名进行硬编码,我已经在模型中避免了使用类似于PropertyChanged.Notify(()=>PropertyName) 我看到的第二个选项是为我的

到目前为止,我的模型实现了
INotifyPropertyChanged
,每个属性都会引发此事件。 几乎所有的视图模型都通过
属性changedeventhandler
监听这些更改

问题是,即使属性更改对视图不重要,也会为模型中的每个更改调用此处理程序

一个选项是检查引发事件的属性。但是,我不喜欢测试PropertyName字符串的想法。它需要对属性名进行硬编码,我已经在模型中避免了使用类似于
PropertyChanged.Notify(()=>PropertyName)

我看到的第二个选项是为我的所有属性实现单个事件:

public event PropertyChangedEventHandler LayerChanged;
public event PropertyChangedEventHandler FieldChanged;
public event PropertyChangedEventHandler LinkDictionaryChanged;

最佳做法是什么?我更喜欢第二种选择

编辑:我尽量说得更具体些

我的模型课是这样工作的:

  public bool IsFeatureLayer
        {
            get { return _isFeatureLayer; }
            set { PropertyChanged.ChangeAndNotify(ref _isFeatureLayer, value, () => IsFeatureLayer);}
        }

所以问题不在于如何使通知调用更安全,因为我已经使用扩展方法来实现这一点,而不需要属性的字符串名称

问题是如何找出谁在不使用字符串的情况下调用了事件

 void _MCDAExtensionPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if(e.PropertyName.Equals("LinkDictionary"){
              //event handling
           }
        }

这是完全不安全的,因为我的模型中属性的名称可能会更改,我必须在不同的位置对其进行修复。

仅适用于您的项目扩展方法,如下所示:

    public static string GetPropertyName<TObj,TRet>(this TObj obj, Expression<Func<TObj,TRet>> expression)
    {
        MemberExpression body = GetMemberExpression(expression);
        return body.Member.Name;
    }
private string name;
public string Name
{
    get { return name; }
    set { SetProperty(ref name, value); }
}

private void SetProperty<T>(ref T field, T value, [CallerMemberName] string callerMemberName = "")
{
    // callerMemberName = "Name" (the property that called it).

    // Set the field value and raise PropertyChanged event.
}
public static class PropertyChangedExtensions
{
    public static void RegisterPropertyHandler<T, TProperty>(this T obj, Expression<Func<T, TProperty>> propertyExpression, PropertyChangedEventHandler handlerDelegate)
        where T : class, INotifyPropertyChanged
    {
        if (obj == null) throw new ArgumentNullException("obj");

        var propertyName = GetPropertyName(propertyExpression);

        obj.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == propertyName && handlerDelegate != null)
                    handlerDelegate(sender, args);
            };
    }

    public static void Notify<T>(this PropertyChangedEventHandler eventHandler, object sender, Expression<Func<T>> propertyExpression)
    {
        var handler = eventHandler;
        if (handler != null) handler(sender, new PropertyChangedEventArgs(GetPropertyName(propertyExpression)));
    }

    private static string GetPropertyName(LambdaExpression propertyExpression)
    {
        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = propertyExpression.Body as UnaryExpression;
            if (unaryExpression == null) 
                throw new ArgumentException("Expression must be a UnaryExpression.", "propertyExpression");

            memberExpression = unaryExpression.Operand as MemberExpression;
        }

        if (memberExpression == null) 
            throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");

        var propertyInfo = memberExpression.Member as PropertyInfo;
        if (propertyInfo == null) 
            throw new ArgumentException("Expression must be a Property.", "propertyExpression");

        return propertyInfo.Name;
    }
}
public class PersonViewModel : INotifyPropertyChanged
{
    public PersonViewModel()
    {
        Address = new AddressViewModel();
        Address.RegisterPropertyHandler(a => a.ZipCode, ZipCodeChanged);
    }

    private AddressViewModel _address;

    public AddressViewModel Address
    {
        get { return _address; }
        set
        {
            _address = value;
            PropertyChanged.Notify(this, () => Address);
        }
    }

    private static void ZipCodeChanged(object sender, PropertyChangedEventArgs args)
    {
        // This will only be called when the 'ZipCode' property of 'Address' changes.
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class AddressViewModel : INotifyPropertyChanged
{
    private string _zipCode;

    public string ZipCode
    {
        get
        {
            return _zipCode;
        }
        set
        {
            _zipCode = value;
            PropertyChanged.Notify(this, () => ZipCode);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
公共静态字符串GetPropertyName(此TObj obj,表达式)
{
MemberExpression body=GetMemberExpression(表达式);
返回body.Member.Name;
}
通过这种方式,您将对属性名、字符串和属性名进行编译检查,但性能却很低。有了这个,您可以调用:

PropertyChanged.Notify(this.getPropertyName(t=>t.PropertyName))


这并不理想,但如果没有字符串,就很难做到这一点。

如果您以.NET 4.5为目标,使用新的
CallerMemberName
属性实现
INotifyPropertyChanged
会更容易、更安全

简而言之,
CallerMemberName
属性允许您获取调用成员的名称作为方法参数。通过这种方式,您可以获得如下内容:

    public static string GetPropertyName<TObj,TRet>(this TObj obj, Expression<Func<TObj,TRet>> expression)
    {
        MemberExpression body = GetMemberExpression(expression);
        return body.Member.Name;
    }
private string name;
public string Name
{
    get { return name; }
    set { SetProperty(ref name, value); }
}

private void SetProperty<T>(ref T field, T value, [CallerMemberName] string callerMemberName = "")
{
    // callerMemberName = "Name" (the property that called it).

    // Set the field value and raise PropertyChanged event.
}
public static class PropertyChangedExtensions
{
    public static void RegisterPropertyHandler<T, TProperty>(this T obj, Expression<Func<T, TProperty>> propertyExpression, PropertyChangedEventHandler handlerDelegate)
        where T : class, INotifyPropertyChanged
    {
        if (obj == null) throw new ArgumentNullException("obj");

        var propertyName = GetPropertyName(propertyExpression);

        obj.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == propertyName && handlerDelegate != null)
                    handlerDelegate(sender, args);
            };
    }

    public static void Notify<T>(this PropertyChangedEventHandler eventHandler, object sender, Expression<Func<T>> propertyExpression)
    {
        var handler = eventHandler;
        if (handler != null) handler(sender, new PropertyChangedEventArgs(GetPropertyName(propertyExpression)));
    }

    private static string GetPropertyName(LambdaExpression propertyExpression)
    {
        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = propertyExpression.Body as UnaryExpression;
            if (unaryExpression == null) 
                throw new ArgumentException("Expression must be a UnaryExpression.", "propertyExpression");

            memberExpression = unaryExpression.Operand as MemberExpression;
        }

        if (memberExpression == null) 
            throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");

        var propertyInfo = memberExpression.Member as PropertyInfo;
        if (propertyInfo == null) 
            throw new ArgumentException("Expression must be a Property.", "propertyExpression");

        return propertyInfo.Name;
    }
}
public class PersonViewModel : INotifyPropertyChanged
{
    public PersonViewModel()
    {
        Address = new AddressViewModel();
        Address.RegisterPropertyHandler(a => a.ZipCode, ZipCodeChanged);
    }

    private AddressViewModel _address;

    public AddressViewModel Address
    {
        get { return _address; }
        set
        {
            _address = value;
            PropertyChanged.Notify(this, () => Address);
        }
    }

    private static void ZipCodeChanged(object sender, PropertyChangedEventArgs args)
    {
        // This will only be called when the 'ZipCode' property of 'Address' changes.
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class AddressViewModel : INotifyPropertyChanged
{
    private string _zipCode;

    public string ZipCode
    {
        get
        {
            return _zipCode;
        }
        set
        {
            _zipCode = value;
            PropertyChanged.Notify(this, () => ZipCode);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
私有字符串名称;
公共字符串名
{
获取{返回名称;}
set{SetProperty(ref name,value);}
}
私有void SetProperty(ref T字段,T值,[CallerMemberName]字符串CallerMemberName=”“)
{
//callerMemberName=“Name”(调用它的属性)。
//设置字段值并引发PropertyChanged事件。
}
您可以看到如何使用它的示例

至于选择哪一个选项,我认为执行时间方面的差异可以忽略不计,而不是因为每个属性都有一个额外的事件而导致的编码开销和代码混乱(代码本身和intellisense)。我肯定会选择第一种

编辑:

不幸的是,在处理
PropertyChanged
事件时,只能对
PropertyName
字符串进行测试,即使属性名称发生更改,也无法以保持一致的方式获取该字符串。对于依赖项属性,您有
MyDependencyProperty.Name
,但这不适用于常规属性


最终,您的选项是为每个属性使用不同的事件,或者在定义包含属性名称的属性的类中定义一个常量,希望您在更改属性名称时记得对其进行修改。假设您没有很多类在自己附加处理程序的地方实现
INotifyPropertyChanged
,那么在这些特定类中为每个属性都设置一个事件并没有那么糟糕。

如果我正确理解您的问题,您可以使用以下方法:

    public static string GetPropertyName<TObj,TRet>(this TObj obj, Expression<Func<TObj,TRet>> expression)
    {
        MemberExpression body = GetMemberExpression(expression);
        return body.Member.Name;
    }
private string name;
public string Name
{
    get { return name; }
    set { SetProperty(ref name, value); }
}

private void SetProperty<T>(ref T field, T value, [CallerMemberName] string callerMemberName = "")
{
    // callerMemberName = "Name" (the property that called it).

    // Set the field value and raise PropertyChanged event.
}
public static class PropertyChangedExtensions
{
    public static void RegisterPropertyHandler<T, TProperty>(this T obj, Expression<Func<T, TProperty>> propertyExpression, PropertyChangedEventHandler handlerDelegate)
        where T : class, INotifyPropertyChanged
    {
        if (obj == null) throw new ArgumentNullException("obj");

        var propertyName = GetPropertyName(propertyExpression);

        obj.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == propertyName && handlerDelegate != null)
                    handlerDelegate(sender, args);
            };
    }

    public static void Notify<T>(this PropertyChangedEventHandler eventHandler, object sender, Expression<Func<T>> propertyExpression)
    {
        var handler = eventHandler;
        if (handler != null) handler(sender, new PropertyChangedEventArgs(GetPropertyName(propertyExpression)));
    }

    private static string GetPropertyName(LambdaExpression propertyExpression)
    {
        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = propertyExpression.Body as UnaryExpression;
            if (unaryExpression == null) 
                throw new ArgumentException("Expression must be a UnaryExpression.", "propertyExpression");

            memberExpression = unaryExpression.Operand as MemberExpression;
        }

        if (memberExpression == null) 
            throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");

        var propertyInfo = memberExpression.Member as PropertyInfo;
        if (propertyInfo == null) 
            throw new ArgumentException("Expression must be a Property.", "propertyExpression");

        return propertyInfo.Name;
    }
}
public class PersonViewModel : INotifyPropertyChanged
{
    public PersonViewModel()
    {
        Address = new AddressViewModel();
        Address.RegisterPropertyHandler(a => a.ZipCode, ZipCodeChanged);
    }

    private AddressViewModel _address;

    public AddressViewModel Address
    {
        get { return _address; }
        set
        {
            _address = value;
            PropertyChanged.Notify(this, () => Address);
        }
    }

    private static void ZipCodeChanged(object sender, PropertyChangedEventArgs args)
    {
        // This will only be called when the 'ZipCode' property of 'Address' changes.
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class AddressViewModel : INotifyPropertyChanged
{
    private string _zipCode;

    public string ZipCode
    {
        get
        {
            return _zipCode;
        }
        set
        {
            _zipCode = value;
            PropertyChanged.Notify(this, () => ZipCode);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

我发现您已经有了一个
Notify
扩展方法,因此您只需要添加
RegisterPropertyHandler
。至少这是一个开始:)

我已经在使用:PropertyChanged.Notify(()=>LinkDictionary)执行此操作了;问题是如何在侦听器端执行此操作:void _MCDAExtensionPropertyChanged(对象发送者,PropertyChangedEventArgs e){if(e.PropertyName.Equals(“MyPropertyName”)什么是
LinkDictionary
确切地说是某种字符串常量?从我的解决方案中可以看出,您没有到处都是字符串。您使用扩展方法从实例中获取字符串。在您的侦听器上,您必须将侦听器的实例添加到。因此,将其添加到
\u MCDAExtensionPropertyChanged(对象发送方,PropertyChangedEventArgs e){if(e.PropertyName.Equals(listeningObj=>listeningObj.GetPropertyName(l=>l.MyPropertyName)
到监听器端的您的属性
MyPropertyName
。我已经在做类似的事情。问题是如何找出谁调用了事件。请参阅我更新的问题。这是可能的…请看khellang的答案。我已经在使用它。问题更多的是如何区分事件发生后可能已更改的属性被提出。这正是
RegisterPropertyHandler
方法通过检查事件的
PropertyName
所做的。你是对的!很抱歉,我没有仔细研究你的示例。但是,它工作起来很有魅力。