所以我有一个旨在实现INotifyPropertyChanged的PropertyBag类。为了使这段代码尽可能干净地工作并避免用户错误,我使用堆栈来获取属性名称。请参阅,如果属性名称与实际属性完全不匹配,那么您将失败并且我正在尝试保护它。
因此,这是该类的示例用法:
public class MyData : PropertyBag
{
public MyData()
{
Foo = -1;
}
public int Foo
{
get { return GetProperty<int>(); }
set { SetProperty(value); }
}
}
基础PropertyBag的重要代码在这里:
public abstract class PropertyBag : INotifyPropertyChanged
{
protected T GetProperty<T>()
{
string propertyName = PropertyName((new StackTrace()).GetFrame(1));
if (propertyName == null)
throw new ArgumentException("GetProperty must be called from a property");
return GetValue<T>(propertyName);
}
protected void SetProperty<T>(T value)
{
string propertyName = PropertyName((new StackTrace()).GetFrame(1));
if (propertyName == null)
throw new ArgumentException("SetProperty must be called from a property");
SetValue(propertyName, value);
}
private static string PropertyName(StackFrame frame)
{
if (frame == null) return null;
if (!frame.GetMethod().Name.StartsWith("get_") &&
!frame.GetMethod().Name.StartsWith("set_"))
return null;
return frame.GetMethod().Name.Substring(4);
}
}
现在您已经看过我的代码,我可以告诉您问题...在某些情况下,在发布版本中,“MyData”构造函数中的“Foo”setter似乎已经过优化,可以内联为SetProperty( - 1)。不幸的是,这个内联优化失败了我的SetProperty方法,因为我不再从属性调用它!失败。看来我不能以这种方式依赖StackTrace。
任何人都可以 答:找出更好的方法来做到这一点,但仍然避免将“Foo”传递给GetProperty和SetProperty? B:想办法告诉编译器在这种情况下不优化吗?
答案 0 :(得分:13)
在这里使用堆栈是缓慢且不必要的;我只想使用:
get { return GetProperty<int>("Foo"); }
set { SetProperty("Foo", value); }
(提示:我已经使用自定义属性模型做了很多工作;我知道这很有效......)
另一种选择是对象键(使用引用相等性来比较) - 很多ComponentModel
都是这样工作的,WF / WPF中的一些属性也是如此:
static readonly object FooKey = new object();
...
get { return GetProperty<int>(FooKey); }
set { SetProperty(FooKey, value); }
当然,您可以声明键的类型(具有Name
属性),并使用:
static readonly PropertyKey FooKey = new PropertyKey("Foo");
等;但是,要回答这个问题:用:
标记它(但不要这样做)[MethodImpl(MethodImplOptions.NoInlining)]
或
[MethodImpl(MethodImplOptions.NoOptimization)]
或
[MethodImpl(MethodImplAttributes.NoOptimization
| MethodImplAttributes.NoInlining)]
答案 1 :(得分:2)
使用堆栈不是一个好主意。您依靠编译器的内部实现来人为地将属性包绑定到语言属性。
MethodImpl
属性会使您的属性包对其他开发人员使用不透明。MethodImpl
属性,也不能保证它将成为调用堆栈中的第一帧。可能已对装配进行检测或修改,以在实际属性和对属性包的调用之间注入调用。 (想想方面编程)'_get'
和'_set'
你应该只是实现你的属性包访问器来获取一个参数来识别属性 - 字符串名称(如Hastable)或对象(如WPF依赖属性包)
答案 2 :(得分:2)
尝试新的[CallerMemberName]属性。
将它放在方法的参数上([CallerMemberName] callerName = null),编译器将重写对您方法的所有调用以自动传递调用者名称(您的调用根本不会传递参数)。
它不会消除任何优化,并且比lambdas或反射或堆栈快得多,并且在发布模式下工作。
P.S。如果您的框架版本中不存在CallerMemberNameAttribute,则只需定义它(空)。它是一种语言功能,而不是框架功能。当编译器在参数上看到[CallerMemberNameAttribute]时,它就可以正常工作。
答案 3 :(得分:1)
如果您希望避免硬编码字符串,可以使用:
protected T GetProperty<T>(MethodBase getMethod)
{
if (!getMethod.Name.StartsWith("get_")
{
throw new ArgumentException(
"GetProperty must be called from a property");
}
return GetValue<T>(getMethod.Name.Substring(4));
}
根据需要添加更多健全性检查
然后属性变为
public int Foo
{
get { return GetProperty<int>(MethodInfo.GetCurrentMethod()); }
}
以同样的方式设置更改。
GetCurrentMethod()也遍历堆栈,但是通过依赖堆栈标记的(内部)非托管调用来执行此操作,因此也可以在发布模式下工作。
或者快速修复[MethodImpl] with MethodImplAttributes.NoOptimization)或MethodImplAttributes.NoInlining也可以使用性能命中(尽管假设每次命中时你都在遍历堆栈帧)。
进一步的技术,获得一定程度的编译时检查是:
public class PropertyHelper<T>
{
public PropertyInfo GetPropertyValue<TValue>(
Expression<Func<T, TValue>> selector)
{
Expression body = selector;
if (body is LambdaExpression)
{
body = ((LambdaExpression)body).Body;
}
switch (body.NodeType)
{
case ExpressionType.MemberAccess:
return GetValue<TValue>(
((PropertyInfo)((MemberExpression)body).Member).Name);
default:
throw new InvalidOperationException();
}
}
}
private static readonly PropertyHelper<Xxxx> propertyHelper
= new PropertyHelper<Xxxx>();
public int Foo
{
get { return propertyHelper.GetPropertyValue(x => x.Foo); }
}
其中Xxxxx是定义属性的类。如果静态性质导致问题(线程或以其他方式使其成为实例值)。
我应该指出,这些技术实际上是出于利益的考虑,我并不是说它们是一种很好的通用技术。
答案 4 :(得分:1)
您可以尝试创建T4模板文件,以便使用GetProperty()和SetProperty()方法的正确属性名称自动生成属性。
T4: Text Template Transformation Toolkit T4 (Text Template Transformation Toolkit) Code Generation - Best Kept Visual Studio Secret