具有操作/条件的属性



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

[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的属性。

如果你必须处理数百个设置(同时存在),但彼此之间存在关系,并且在其他一些条件下存在额外的关系,你的解决方案是什么?如何在不创建AdminAttributeElementAttribute的情况下创建AdminElement属性行为?

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

这不是个好主意

做你所描述的事情是不合理的困难,即使你成功地做到了,结果也无法维持

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

属性参数被限制为以下类型:

  • 标量类型(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. 编译器不检查属性名称;调用方和CCD_ 20的实现都可能犯错误(例如简单的拼写错误),而这些错误不会被标记
  2. 目前还不清楚哪些属性是有条件的,哪些不是仅通过查看它们的声明
  3. 属性和条件之间的确切关系在某种程度上是隐藏的

也具有编译时安全性

如果以上内容不令人满意,您可以通过消除前两个问题并改进第三个问题来进行改进,只需少量的运行时成本。这个想法基于一个众所周知的技巧,即在引用属性名时提供编译时的安全性:不要将它们指定为字符串,而是将它们指定作为成员访问表达式。

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

您可以将以上内容调用为IsPropertyApplicable(o => o.SomeSetting),并在运行时使用((MemberExpression)expr.Body).Member.Name"SomeSetting"作为字符串。然而,我们并不想在任何时候使用裸字符串,因为这意味着上面的问题#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的声明就无法判断它是否有条件可见。

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

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

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

在我的情况下,到目前为止最令人满意的解决方案是声明另一个后缀为Hide的属性,该属性包含条件检查代码,并通过ConfigWrapper中的反射来检查是否添加此设置:

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

这个设置必须声明为protected(一开始我犯了一个愚蠢的错误),以便在公共设置中隐藏它本身。

然后在配置包装器中:

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

您可以查看以下框架:

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

这有许多动态特征。

最新更新