是否可以指定实现以下目标:
[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
的属性。
如果您必须处理数百个设置(一次存在),但是彼此之间的关系以及其他一些条件还需要处理,那么您的解决方案是什么?如何在不创建Admin
和Element
的情况下创建AdminAttribute
和ElementAttribute
属性行为?
重点是,有多种不同的配置(继承自基本配置),我希望其中一些能够自由地使用部分代码指定可见性条件,如果评估为false,将隐藏设置,没有创建数十个基于IHide
的属性。在定义设置本身时进行声明性编程!
答案 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;
}
}
}
这将完成这项工作但它确实有一些缺点:
IsPropertyApplicable
的实现都可能出错(例如简单的拼写错误),这些错误不会被标记。如果上述情况不能令人满意,您可以通过消除前两个问题来改进它,并在运行成本较低的情况下改进第三个问题。在引用属性名称时,这个想法基于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)