除了 INotifyPropertyChanged 的 PropertyChanged 事件的属性名称之外,更改属性名称并期望 Visual Studio 中的重命名功能负责所有必要的重命名,这一定是一个比较常见的事件。有没有更好的方法让它强类型化,这样你就不需要记住手动重命名它?
7 回答
编辑: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);
}
}
}
C# 5 似乎有一个解决方案。具有可与参数一起使用的CallerMemberName
属性(网上的一个示例)。
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 ) );
}
}
}
最简单的解决方案是查看堆栈跟踪并完全删除对该属性的每个显式引用。
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_<PropertyName>
。如果编译器不再遵循这个命名约定,代码就会中断。
另一种解决方案是从 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
”。代码的生产版本确实需要额外的检查并更好地集成到代码的其余部分中。例如,可以对泛型参数使用类型推断。
更新
还有第三种解决方案 - 您可以MethodBase.GetCurrentMethod()
在属性内部使用 getter 或 setter 来获取 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;
理论上,您可以在属性设置器中使用 MethodBase.GetCurrentMethod().Name.Substring(4)。不幸的是,谷歌搜索显示它似乎对性能产生了重大影响。还有两点需要考虑:
- 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() 运算符会很好。我相信它将极大地提高需要该能力的项目中代码分析/重构工具的可靠性。
不是您的问题的答案,但是如果您右键单击->重构->重命名属性,它也可以重命名匹配的字符串,包括与您的属性名称匹配的任何字符串。
是的,这可能有点危险。
你应该看看这篇博文。它使您能够执行此操作:
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 中的重命名应该适合你。
PropertyChangedEventArgs仅采用一个构造函数,它需要将属性名称作为字符串。因此,基本上不使用 INotifyPropertyChanged 意味着在某种程度上,无论是架构的高位还是低位,您都必须使用字符串和手动重命名。