7

所以我有一个旨在实现 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”设置器似乎被优化为内联为 SetProperty(-1)。不幸的是,这种内联优化使我的 SetProperty 方法失败,因为我不再从属性中调用它!失败。看来我不能以这种方式依赖 StackTrace。

任何人都可以 A:找出更好的方法来做到这一点,但仍然避免将“Foo”传递给 GetProperty 和 SetProperty?
B:想办法告诉编译器在这种情况下不要优化?

4

5 回答 5

14

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

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

ETC; 但是,要回答这个问题:标记它(但要这样做):

[MethodImpl(MethodImplOptions.NoInlining)]

或者

[MethodImpl(MethodImplOptions.NoOptimization)]

或者

[MethodImpl(MethodImplAttributes.NoOptimization
    | MethodImplAttributes.NoInlining)]

于 2009-02-17T16:38:50.490 回答
2

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

  1. 要求添加MethodImpl属性会使您的属性包对其他开发人员不透明。
  2. 即使属性包具有该MethodImpl属性,也无法保证它将成为调用堆栈上的第一帧。程序集可能被检测或修改以在实际属性和对您的属性包的调用之间注入调用。(思考方面编程)
  3. 新语言甚至 C# 编译器的未来版本可能会以不同的方式装饰属性访问器,'_get'然后'_set'
  4. 构造调用栈是一个比较慢的操作,因为它需要对内部压缩栈进行解压,并通过反射获取每个类型和方法的名称。

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

于 2009-02-17T16:57:56.967 回答
2

尝试新的 [CallerMemberName] 属性。

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

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

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

于 2014-07-28T23:20:09.470 回答
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() 也遍历堆栈,但通过依赖堆栈标记的(内部)非托管调用来执行此操作,因此也可以在发布模式下工作。

或者,对于使用 MethodImplAttributes.NoOptimization) 或 MethodImplAttributes.NoInlining 的快速修复 [MethodImpl] 也可以工作,尽管性能会受到影响(尽管每次该命中可以忽略不计时,您都在遍历堆栈帧)。

另一种获得某种程度的编译时检查的技术是:

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 是定义属性的类。如果静态性质导致问题(线程或以其他方式使其成为实例值也是可能的)。

我应该指出,这些技术真的是为了利益,我并不是说它们是很好的通用技术。

于 2009-02-17T16:56:15.473 回答
1

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

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

于 2009-02-17T17:44:37.010 回答