【问题标题】:Is there a good strongly typed way to do PropertyChanged events in C#?在 C# 中是否有一种很好的强类型方法来执行 PropertyChanged 事件?
【发布时间】:2010-11-10 19:34:32
【问题描述】:

更改属性的名称并期望 Visual Studio 中的重命名功能负责所有必要的重命名,这一定是一个比较常见的事件,但 INotifyPropertyChanged 的​​ PropertyChanged 事件的属性名称除外。有没有更好的方法让它强类型化,这样你就不需要记住手动重命名它?

【问题讨论】:

标签: c# inotifypropertychanged strong-typing


【解决方案1】:

编辑:nameof 到达 c# 6。耶!


没有nameof/infoof等;这已经讨论了很多,但它就是这样。

有一种方法可以在 .NET 3.5 中使用 lambda 表达式(并解析表达式树),但实际上它不值得开销。现在,我只会坚持使用字符串(如果您确定不破坏它,还可以进行单元测试)。


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);
        }
    }
}

【讨论】:

  • 我可能只是坚持使用字符串,但为了踢球,您能否提供一个关于如何解析表达式树的简短代码示例?
  • 你为什么使用Expression&lt;Func&lt;Program, T&gt;&gt;p=&gt;p.Name而不是Expression&lt;Func&lt;T&gt;&gt;()=&gt;Name
  • 两者都可以,但发布的版本清楚地表明我们正在寻找实例上的属性 - 而不仅仅是随机的。
  • 在基本视图模型类上指定 Expression> 将不允许子类提升它的属性。这就是为什么 Expression> 更适合此目的。
  • 不再是真的!现在你可以使用nameof了。
【解决方案2】:

C# 5 似乎有一个解决方案。带有可与参数一起使用的CallerMemberName attribute (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 ) );
        }
    }
}

【讨论】:

    【解决方案3】:

    最简单的解决方案是查看堆栈跟踪并完全删除对该属性的每个显式引用。

    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));
        }
    }
    

    代码通过堆栈跟踪从校准方法派生属性名称 - 即名为 set_&lt;PropertyName&gt; 的属性设置器方法。如果编译器不再遵循这个命名约定,代码就会中断。

    另一种解决方案是从 lambda 表达式派生属性名称。

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

    例如

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

    将按预期返回“Length”。代码的生产版本确实需要额外的检查并更好地集成到代码的其余部分中。例如,可以对泛型参数使用类型推断。

    更新

    还有第三种解决方案 - 您可以在属性 getter 或 setter 中使用 MethodBase.GetCurrentMethod() 来获取 setter 或 getter 方法的名称。

    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;
    

    【讨论】:

    • 这个解决方案比简单地将属性名称设置为字符串更脆弱。
    • 这看起来很有趣。但是,如果不检查文档,我无法完全弄清楚对 SubString 的调用是为了什么。 GetMethod().Name 会返回一些奇怪的东西吗?
    • 一个属性由两种方法实现。公共 MyType MyProperty { 获取;放; } 实现为 public void set_MyProperty(MyType value) { } 和 public MyType get_MyProperty()。因此,您必须从返回的方法名称中删除 set_ 和 get_ 以获取属性的名称。
    【解决方案4】:

    理论上,您可以在属性设置器中使用 MethodBase.GetCurrentMethod().Name.Substring(4)。不幸的是,谷歌搜索显示it seems to have a significant performance impact。还有两点需要考虑:

    • JIT 内联可能会以意想不到的方式影响这一点。 (stackoverflow.com/questions/616779/can-i-check-if-the-c-compiler-inlined-a-method-call)
    • 理论上,对 MethodBase.GetCurrentMethod() 的 IL 调用可以在运行时由 JIT 简单地替换为 ldtoken 指令,然后调用 MethodBase.GetMethodFromHandle(),这将非常快。我猜用户只是没有表达对此的需求。 (msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldtoken.aspx)
    • 这里完全是我的观点,但我认为在 C# 中使用 fieldof() 和 methodof() 运算符会很好。我相信它将极大地提高需要该能力的项目中代码分析/重构工具的可靠性。

    【讨论】:

      【解决方案5】:

      不是您问题的答案,但如果您右键单击->重构->重命名属性,它也可以重命名匹配的字符串,包括与您的属性名称匹配的任何字符串。

      是的,这可能有点危险。

      【讨论】:

      • 即使在 cmets 中重命名也是危险的。我在一个相当大的项目中搞砸了一堆 XML 文档,假设此功能会将范围限制在被重命名的代码元素上/中的 cmets。
      【解决方案6】:

      你应该看看这个blog post。它使您能够做到这一点:

      string propertyName = TypeHelper.GetPropertyName<User>(u => u.LastProjectCode);
      
      PropertyInfo property1 = TypeHelper.GetProperty((SomeClass o) => o.InstanceProperty.Length);
      
      PropertyInfo property2 = TypeHelper.GetProperty(() => SomeClass.StaticProperty.Length);
      

      Visual Studio/Resharper/Refactor Pro 中的重命名应该适合你。

      【讨论】:

        【解决方案7】:

        PropertyChangedEventArgs 只接受一个构造函数,它需要将属性名称作为字符串。因此,基本上不使用 INotifyPropertyChanged 意味着在某种程度上,无论是架构的高位还是低位,您都必须使用字符串和手动重命名。

        【讨论】:

          猜你喜欢
          • 2013-07-09
          • 2011-03-09
          • 2010-12-24
          • 2012-04-17
          • 1970-01-01
          • 2019-11-17
          • 1970-01-01
          • 2010-10-11
          • 2020-03-30
          相关资源
          最近更新 更多