有什么方法可以避免C#中的Property内联优化?

所以我有一个旨在实现INotifyPropertyChanged的PropertyBag类。 为了使这段代码尽可能干净地工作并避免用户错误,我使用堆栈来获取属性名称。 请参阅,如果属性名称与实际属性完全不匹配,那么您将失败并且我正在尝试保护它。

所以,这是该类的示例用法:

public class MyData : PropertyBag { public MyData() { Foo = -1; } public int Foo { get { return GetProperty(); } set { SetProperty(value); } } } 

基本PropertyBag的重要代码在这里:

 public abstract class PropertyBag : INotifyPropertyChanged { protected T GetProperty() { string propertyName = PropertyName((new StackTrace()).GetFrame(1)); if (propertyName == null) throw new ArgumentException("GetProperty must be called from a property"); return GetValue(propertyName); } protected void SetProperty(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:想办法告诉编译器在这种情况下不优化?

在这里使用堆栈是缓慢且不必要的; 我会简单地使用:

 get { return GetProperty("Foo"); } set { SetProperty("Foo", value); } 

(提示:我已经使用自定义属性模型做了很多工作;我知道这很有效……)

另一个替代方法是对象键(使用引用相等进行比较) – 许多ComponentModel这种方式工作,WF / WPF中的一些属性也是如此:

 static readonly object FooKey = new object(); ... get { return GetProperty(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. 要求添加MethodImpl属性会使您的属性包对其他开发人员不透明。
  2. 即使属性包具有MethodImpl属性,也没有什么能保证它将成为调用堆栈的第一帧。 可能已对assembly进行检测或修改,以在实际属性和对属性包的调用之间注入调用。 (想想方面编程)
  3. 新的语言甚至是未来版本的C#编译器可能会以不同的方式装饰属性访问器,然后是'_get''_set'
  4. 构造调用堆栈的操作相对较慢,因为它需要对内部压缩堆栈进行解压缩,并使用reflection获取每种类型和方法的名称。

你应该真正实现你的属性包访问器来获取一个参数来识别属性 – 字符串名称(如Hastable)或对象(如WPF依赖属性包)

尝试新的[CallerMemberName]属性。

将它放在方法的参数上([CallerMemberName] callerName = null),编译器将重写对方法的所有调用以自动传递调用者名称(您的调用根本不会传递参数)。

它不会消除任何优化,并且比lambdas或reflection或堆栈快得多,并且在Release模式下工作。

PS如果你的版本的框架中不存在CallerMemberNameAttribute,只需定义它(空)。 它是一种语言function,而不是框架function。 当编译器在参数上看到[CallerMemberNameAttribute]时,它就可以正常工作。

您可以尝试创建T4模板文件,以便使用GetProperty()和SetProperty()方法的正确属性名称自动生成属性。

T4:文本模板转换工具包 T4(文本模板转换工具包)代码生成 – 最佳保持Visual Studio秘密

如果您希望避免硬编码字符串,您可以使用:

 protected T GetProperty(MethodBase getMethod) { if (!getMethod.Name.StartsWith("get_") { throw new ArgumentException( "GetProperty must be called from a property"); } return GetValue(getMethod.Name.Substring(4)); } 

根据需要添加更多健全性检查

然后财产变得

 public int Foo { get { return GetProperty(MethodInfo.GetCurrentMethod()); } } 

以同样的方式设置变化。

GetCurrentMethod()也遍历堆栈,但是通过依赖堆栈标记的(内部)非托管调用来执行此操作,因此也将在发布模式下工作。

或者,对于快速修复[MethodImpl] with MethodImplAttributes.NoOptimization)或MethodImplAttributes.NoInlining也会起作用,但是每次此命中可忽略不计时,您将遍历堆栈帧。

进一步的技术,获得一定程度的编译时检查是:

 public class PropertyHelper { public PropertyInfo GetPropertyValue( Expression> selector) { Expression body = selector; if (body is LambdaExpression) { body = ((LambdaExpression)body).Body; } switch (body.NodeType) { case ExpressionType.MemberAccess: return GetValue( ((PropertyInfo)((MemberExpression)body).Member).Name); default: throw new InvalidOperationException(); } } } private static readonly PropertyHelper propertyHelper = new PropertyHelper(); public int Foo { get { return propertyHelper.GetPropertyValue(x => x.Foo); } } 

其中Xxxxx是定义属性的类。 如果静态性质导致问题(线程或以其他方式使其成为实例值也是可能的)。

我应该指出,这些技术实际上是出于利益的考虑,我并不是说它们是一种很好的通用技术。