代码之家  ›  专栏  ›  技术社区  ›  Sinatr

具有动作/条件的属性

  •  5
  • Sinatr  · 技术社区  · 10 年前

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

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

    哪里 条件 是什么东西 复杂的 ? 例如,

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

    我正在使用 PropertyGrid 将配置显示/编辑为某些可序列化类的财产。我需要 级联 :设置某些设置时,可能会隐藏其他一些设置,具体取决于值。

    目前,我可以这样隐藏一些设置:

    • 创建新属性,基于 IHide
    • 将其分配给所需的财产
    • 检查中给定属性的所有属性 ConfigWrapper ,如果有 I隐藏 键入,然后检查 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 我想,但那会很邋遢。可以枚举所有属性和缓存条件。但也许有一种更容易/替代的方法?还有其他想法吗?

    启动赏金

    我正在寻找将多种 I隐藏 属性( AdminAttribute -当用户是管理员时显示设置, ElementAttribute -当指定元素出现时显示设置等) 超级属性 。我希望能够以某种方式指定条件,而无需创建新的 I隐藏 的基于属性 每个案例 .

    如果您必须处理数百个设置(同时存在),但彼此之间的关系又与其他条件相关,那么您的解决方案是什么?如何创建 Admin Element 属性行为而不创建 管理员属性 元素属性 ?

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

    3 回复  |  直到 10 年前
        1
  •  8
  •   Community SushiHangover    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. 财产和条件之间的确切关系有点隐蔽。

    也具有编译时安全性

    如果上述情况不令人满意,可以通过消除前两个问题并以较小的运行时成本改进第三个问题来进行改进。这个想法基于 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(财产和条件更加明显)。它仍然没有解决第二个问题:你无法判断 SomeProperty 仅通过查看其声明即可有条件地看到。

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

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

    正如@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 ),将其自身隐藏在公共设置中。

    然后在配置包装器中:

        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));
            }
        }
    
        3
  •  0
  •   Mizan    10 年前