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

时间:2009-02-17 16:32:39

标签: c# stack-trace

所以我有一个旨在实现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:想办法告诉编译器在这种情况下不优化吗?

5 个答案:

答案 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)

使用堆栈不是一个好主意。您依靠编译器的内部实现来人为地将属性包绑定到语言属性。

  1. 要求添加MethodImpl属性会使您的属性包对其他开发人员使用不透明。
  2. 即使属性包具有MethodImpl属性,也不能保证它将成为调用堆栈中的第一帧。可能已对装配进行检测或修改,以在实际属性和对属性包的调用之间注入调用。 (想想方面编程)
  3. 新语言甚至未来版本的C#编译器可能会以不同的方式装饰属性访问器,然后'_get''_set'
  4. 构造调用堆栈的操作相对较慢,因为它需要解压缩内部压缩堆栈,并使用反射获取每种类型和方法的名称。
  5. 你应该只是实现你的属性包访问器来获取一个参数来识别属性 - 字符串名称(如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