2007年4月1日星期日

the order of attributes reflected

更正一个错误

在前一篇“Upgrade to support multi validator attributes”中的最后一个部分中提到需要注意反射得到的属性之间的先后顺序问题:

需要注意的地方

[RegularValidator("^[0-9]$")]
[RequiredField]
private System.Windows.Forms.TextBox textBox2;

看两个属性的位置,它们不是随意摆放,要求属性之间有一定的先后顺序。这主要是因为MemberInfo.GetAttributes方法返回的属性是按一定的顺序排序的,当我们在注册事件的时候,会按照GetAttributes返回的属性数组中属性的先后顺序来为控件注册属性中的ValidatingDelegate。具体的顺序是这样的:

“离属性的目标字段(也就是要附加属性的字段)越近的属性,在反射得到的集合中位置越靠前”

也就是说如果我们要让某个属性最先起作用,那么就得让它里目标字段最近,比如例子中的RequiredField

可 是后来我发现把UserControl加入之后,Form中属性的顺序是遵从上面说的,但是UserControl的属性似乎不是那么严格。在发表这篇文 章的时候测是通过的,可是在我昨天的使用中发现UserControl中不正常的问题,不知道是因为环境的不同还是其他的什么原因。但是不管怎么样我们总 是不能依赖属性的顺序了,为了不造成误导,我必须将上面的话删除。

新的解决办法

在不能依赖属性的顺序的情况下,如何保证附加到控件上的属性所做的限定都能得到满足呢?根据上一篇中的阐述,一个属性实际上是对应到了控件的一个Validating事件,要限定属性的顺序主要是为了限定控件的Validating执行的顺序。

Validating事件乱序带来的问题

还是使用前面的例子,要给一个TextBox加2个输入的限定:
[RegularValidator("^[0-9]$")]
[RequiredField]
private System.Windows.Forms.TextBox textBox2;

我们希望这个文本框的内容不为空而且是0-9之间的一位数字。我们希望的是先执行RequiredField的检查,然后执行RegularValidator("^[0-9]$")的检查。但是如果乱序话,有可能RegularValidator("^[0-9]$")的检查在前面,这样会产生什么问题呢?比如我们输入的是“not empty”:

protected  void Validate(object sender, CancelEventArgs e)
{
e.Cancel =(!IsValid(sender));
}

上面的检查会执行两次。RegularValidator先执行,由于输入不是0-9的数字,所以检验失败:
e.Cancel=false;
然后RequiredField执行,由于输入不是空的,检验合法了:
e.Cancel=true;
看 起来似乎两次检验是两个事件,不会互相影响,但是实际上不是这样的。事件注册使用的操作符"+="实际上是将同类型事件的多个处理者 (EventHandler)串成了一个链【参考Delegate.Combine(params Delegate[] delegates)】,一旦数据源出发一个事件这个链中的处理者会相继得到事件的引用然后进行各自的处理,这也是多播委托的特性。这么说来虽然是进行多次的处理,但是它们处理的仍然是同一个事件的引用。 回到例子中,虽然是两次检验,但是是对于文本框的同一次触发事件的处理,也就是说它们处理的“e”是同一个对象。这样看来“e.Cancel”的值是先被 修改为false,然后又被置为true,那么检验应该是合法的。实施也证明了输入“not empty”的时候不会报告检验失败。

不依赖验证顺序

乱许的时候出现错误,究其原因主要是后面一次的处理覆盖了前面的处理结果,只要我们在后一次的验证中把前面已经得到的结果一起验证就可以了。对Validate处理代码稍作修改即可:

protected  void Validate(object sender, CancelEventArgs e)
{
//should pass every validation
e.Cancel =(e.Cancel|| !IsValid(sender));
   }
现在的验证要想pass,除了满足本次检验以外以前的检验也必须是pass的。这样的修改不再依赖于验证的顺序。

没有评论: