具有动作/条件的属性

时间:2014-02-20 08:59:56

标签: c# properties attributes propertygrid

是否可以指定实现以下目标:

[SomeAttribute(condition1)]
public SomeType SomeSetting1 {get; set;}

[SomeAttribute(condition2)]
public SomeType SomeSetting2 {get; set;}

其中条件复杂的东西?例如,

[SomeAttribute(SomeSetting3 == 4 && SomeSetting4 < 100)]

我使用PropertyGrid来显示/编辑配置作为某些可序列化类的属性。我需要有一些级联:当设置某些设置时,其他一些设置可能会隐藏,具体取决于值。

目前,我可以通过这种方式隐藏一些设置:

  • 根据IHide
  • 创建新属性
  • 将其分配给所需的属性
  • 检查ConfigWrapper中给定属性的所有属性,如果有IHide类型的任何属性,则检查其Hide以确定何时显示(添加到结果集合中)属性)或不。

    public interface IHide
    {
        bool Hide { get; }
    }
    
    public class AdminAttribute : Attribute, Common.IHide
    {
        public bool Hide
        {
            get { return !MySettings.Admin; }
        }
    
        public override object TypeId { get { return "AdminAttributeId"; } }
    }
    
    // admin only setting
    [Admin]
    public SomeType SomeSetting {get; set;}
    

这样我必须为任何新设置(必须隐藏其他一些设置)或组合添加新属性(这就是为什么我想要更多泛型)。当然有时我可以使用属性参数,以便能够将一个属性用于几个类似的目的:

public class ElementAttribute : Attribute, Common.IHide
{
    private string _element;
    public bool Hide
    {
        get { return !Something.Instance.IsElement(_element); }
    }

    public ElementAttribute(string element)
    {
        _element = element;
    }

    public override object TypeId { get { return "ElementAttributeId"; } }
}

通过使用此属性,我可以指定元素符号:

 // setting will be shown if element a present
 [Element('a')]
 public SomeType SomeSetting {get; set;}

在创建了多个这样的内容之后,我开始意识到,代码可能以某种方式将Hide()方法条件纳入属性参数本身?或者或许以某种方式指定行为(行动)?

我可以很容易地使用CodeDom我认为这样做,但它会非常懒散。可以枚举所有属性和缓存条件。但也许有一种更容易/替代的方式?还有其他想法吗?

开始赏金

我正在搜索想法,将多个IHide属性(AdminAttribute - 用户管理时显示设置,ElementAttribute - 显示指定元素显示时的设置等)合并为一个超属性。我希望能够以某种方式指定条件,而无需为每种情况创建新的基于IHide的属性。

如果您必须处理数百个设置(一次存在),但是彼此之间的关系以及其他一些条件还需要处理,那么您的解决方案是什么?如何在不创建AdminElement的情况下创建AdminAttributeElementAttribute属性行为?

重点是,有多种不同的配置(继承自基本配置),我希望其中一些能够自由地使用部分代码指定可见性条件,如果评估为false,将隐藏设置,没有创建数十个基于IHide的属性。在定义设置本身时进行声明性编程!

3 个答案:

答案 0 :(得分:7)

这不是个好主意

做一些你所描述的事情是不合理的,即使你设法做到了,结果也无法维持。

困难源于对属性参数的限制:

  

属性参数限制为常量值   以下类型:

     
      
  • 标量类型(bool,byte,char,short,int,long,float和double)
  •   
  • string
  •   
  • System.Type
  •   
  • 枚举
  •   
  • object(必须是上述类型之一的常量值)
  •   
  • 任何上述类型的一维阵列
  •   

很明显,将谓词压缩为上述任何类型的唯一方法是编写一个字符串a-la SQL,例如

[Hide("foo = \"42\" && !bar")]
public object MyProperty { get; set; }

然后,您需要在运行时解析此字符串,将其转换为可用机器的形式并确定结果是什么。即使这样,编写无效谓词也会非常容易,因为字符串对编译器完全不透明。

但还有其他选择

您尝试的解决方案实际上是在尝试与当前游戏相关 - 属性并不意味着封装运行时行为。为什么不简单地让你的可序列化类实现一个合适的接口呢?例如,您可以从沼泽标准

开始
public interface IConditionalPropertySource
{
    bool IsPropertyApplicable(string propertyName);
}

class Test : IConditionalPropertySource
{
    public string SomeSetting { get; set; }

    public bool IsPropertyApplicable(string propertyName)
    {
        switch (propertyName)
        {
            case "SomeSetting":return DateTime.Now.DayOfWeek == DayOfWeek.Friday;
            default: return false;
        }
    }
}

这将完成这项工作但它确实有一些缺点:

  1. 编译器不检查属性名称;调用者和IsPropertyApplicable的实现都可能出错(例如简单的拼写错误),这些错误不会被标记。
  2. 目前还不清楚哪些属性是有条件的,哪些属性不仅仅是通过查看它们的声明。
  3. 属性和条件之间的确切关系有些隐藏。
  4. 编译时安全性

    如果上述情况不能令人满意,您可以通过消除前两个问题来改进它,并在运行成本较低的情况下改进第三个问题。在引用属性名称时,这个想法基于well-known trick to provide compile-time safety:而不是将它们指定为字符串,将它们指定为成员访问表达式。

    public interface IConditionalPropertySource<T>
    {
        bool IsPropertyApplicable(Expression<Func<T, object>> expr);
    }
    

    您可以将上述内容称为IsPropertyApplicable(o => o.SomeSetting),并在运行时使用"SomeSetting"((MemberExpression)expr.Body).Member.Name作为字符串。但是,我们实际上并不想在任何时候使用裸字符串,因为这意味着上面的问题#1仍然存在。

    因此,我们可以创建一个将成员访问表达式映射到布尔函数的字典,并提供一个相等比较器来替换成员名称相等的表达式(引用相等)的默认相等语义:

    class Test : IConditionalPropertySource<Test>
    {
        // Your properties here:
        public string SomeSetting { get; set; }
    
        // This is the equality comparer used for the dictionary below
        private class MemberNameComparer :
            IEqualityComparer<Expression<Func<Test, object>>>
        {
            public bool Equals(
                Expression<Func<Test, object>> lhs, 
                Expression<Func<Test, object>> rhs)
            {
                return GetMemberName(lhs).Equals(GetMemberName(rhs));
            }
    
            public int GetHashCode(Expression<Func<Test, object>> expr)
            {
                return GetMemberName(expr).GetHashCode();
            }
    
            private string GetMemberName(Expression<Func<Test, object>> expr)
            {
                return ((MemberExpression)expr.Body).Member.Name;
            }
        }
    
        // A dictionary that maps member access expressions to boolean functions
        private readonly IDictionary<Expression<Func<Test, object>>, Func<bool>> 
            conditions = new Dictionary<Expression<Func<Test, object>>, Func<bool>>
            (new MemberNameComparer())
            {
                // The "SomeSetting" property is only visible on Wednesdays
                { 
                    self => self.SomeSetting, 
                    () => DateTime.Now.DayOfWeek == DayOfWeek.Wednesday
                }
            };
    
    
        // This implementation is now trivial
        public bool IsPropertyApplicable(Expression<Func<Test, object>> expr)
        {
            return conditions[expr]();
        }
    }
    

    这消除了问题#1(您不能再拼错属性名称,编译器会捕获它)并且它在#3上得到改进(属性和条件更加明显)。它仍然没有解决问题#2:你只能通过查看它的声明来判断SomeProperty是否有条件可见。

    但是,您可以扩展代码以在运行时强制执行此操作:

    • 使用自定义属性
    • 装饰有条件可见的属性
    • 在构造函数中,枚举用该属性修饰的类的所有属性以及可以从字典键派生的所有属性名称
    • 将两个枚举的集合视为集合
    • 如果集合不相等,则已装饰的属性与已定义条件可见性逻辑的属性之间存在不匹配;抛出异常

答案 1 :(得分:0)

正如@Jon已经指出的那样,使用属性不是正确的方法。

就我而言,到目前为止最令人满意的解决方案是声明另一个带有后缀Hide的属性,该属性包含条件检查代码,并通过ConfigWrapper中的反射用来检查何时添加此设置或不:

public SomeType SomeSetting1 { get; set; }

public SomeType SomeSetting2 { get; set; }
protected SomeType SomeSetting2Hide { get { return SomeSetting3 = 4 && SomeSettings4 < 100; } }

此设置必须声明为protected(起初我制作了stupid mistake),以便将其自身隐藏在公共设置中。

然后在config wrapper中:

    public ConfigWrapper(object obj)
    {
        _original = obj;
        // copy all properites
        foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(obj))
        {
            // filter hideable attributes
            bool add = true;
            foreach (Attribute attribute in property.Attributes)
                if (attribute is Common.IHide && (attribute as Common.IHide).Hide)
                {
                    add = false;
                    break;
                }

            ////////////////////////

            // filter configurable via hide property properties
            var hide = obj.GetType().GetProperty(property.Name + "Hide", BindingFlags.Instance | BindingFlags.NonPublic);
            if (hide != null && (bool)hide.GetValue(obj, null))
                add = false;

            ///////////////////////

            // add
            if (add)
                _collection.Add(new ConfigDescriptor(property));
        }
    }

答案 2 :(得分:0)

你可以看一下这个框架:

http://www.codeproject.com/Articles/415070/Dynamic-Type-Description-Framework-for-PropertyGri

这有很多动态的特色。