一些澄清。这些数据类在基本类型之上设置了一层,限制数据使其有意义。
RegexConstrainedString
实际上,它们位于和类之上ConstrainedNumber<T>
,这就是我们正在讨论将构造函数的验证代码重构为单独的方法的地方。
重构验证代码的问题在于,验证所需的正则表达式仅存在于 的子类中RegexConstrainedString
,因为每个子类都有不同的Regex
. 这意味着验证数据仅可用于RegexConstrainedString
的构造函数,而不是它的任何方法。因此,如果我排除验证代码,调用者将需要访问Regex
.
public class Password: RegexConstrainedString
{
internal static readonly Regex regex = CreateRegex_CS_SL_EC( @"^[\w!""#\$%&'\(\)\*\+,-\./:;<=>\?@\[\\\]\^_`{}~]{3,20}$" );
public Password( string value ): base( value.TrimEnd(), regex ) {} //length enforced by regex, so no min/max values specified
public Password( Password original ): base( original ) {}
public static explicit operator Password( string value ) {return new Password( value );}
}
因此,当从数据库读取值或读取用户输入时,Password 构造函数将其转发Regex
给基类以处理验证。另一个技巧是它会自动修剪结束字符,以防数据库类型是 char 而不是 varchar,所以我不必记住这样做。无论如何,RegexConstrainedString 的主要构造函数如下所示:
protected RegexConstrainedString( string value, Regex subclasses_static_regex, int? min_length, int? max_length )
{
_value = (value ?? String.Empty);
if (min_length != null)
if (_value.Length < min_length)
throw new Exception( "Value doesn't meet minimum length of " + min_length + " characters." );
if (max_length != null)
if (_value.Length > max_length)
throw new Exception( "Value exceeds maximum length of " + max_length + " characters." );
value_match = subclasses_static_regex.Match( _value ); //Match.Synchronized( subclasses_static_regex.Match( _value ) );
if (!value_match.Success)
throw new Exception( "Invalid value specified (" + _value + "). \nValue must match regex:" + subclasses_static_regex.ToString() );
}
由于调用者需要访问子类的Regex
,我认为我最好的选择是IsValueValid
在子类中实现一个方法,它将数据转发到基类IsValueValid
中的方法。RegexConstrainedString
换句话说,我会将这一行添加到 Password 类中:
public static bool IsValueValid( string value ) {return IsValueValid( value.TrimEnd(), regex, min_length, max_length );}
但是我不喜欢这样,因为我正在复制子类构造函数代码,必须记住再次修剪字符串并在必要时传递相同的最小/最大长度。这个要求将被强加于 RegexConstrainedString 的所有子类,这不是我想做的事情。像 Password 这样的数据类非常简单,因为 RegexConstrainedString 处理了大部分工作,实现了运算符、比较、克隆等。
此外,分解代码还有其他复杂性。验证涉及在实例中运行和存储正则表达式匹配,因为某些数据类型可能具有报告字符串特定元素的属性。例如,我的 SessionID 类包含像 TimeStamp 这样的属性,它从存储在数据类实例中的 Match 返回匹配的组。底线是这个静态方法是一个完全不同的上下文。由于它本质上与构造函数上下文不兼容,构造函数不能使用它,所以我最终会再次复制代码。
所以...我可以通过复制验证代码并将其调整为静态上下文并对子类施加要求,或者我可以让事情变得更简单,只执行对象构造。分配的相对额外内存将是最小的,因为实例中只存储了一个字符串和匹配引用。无论如何,其他所有内容,例如 Match 和字符串本身仍将由验证生成,因此无法解决。 我可以整天担心性能,但我的经验是正确的更重要的是,因为正确性通常会导致许多其他优化。例如,我不必担心流经我的应用程序的数据格式或大小不正确,因为只使用有意义的数据类型,这会强制验证从其他层进入应用程序的入口点,无论是数据库或用户界面。我 99% 的验证代码作为不再需要的工件被删除,我发现自己现在只检查空值。顺便说一句,已经到了这一点,我现在明白为什么包含空值是十亿美元的错误. 似乎是我唯一需要检查的东西了,即使它们在我的系统中基本上不存在。将这些数据类型作为字段的复杂对象不能用空值构造,但我必须在属性设置器中强制执行这一点,这很烦人,因为否则它们永远不需要验证代码......只有响应值变化而运行的代码.
更新:我模拟了CLR函数的双向调用,发现当所有数据都有效时,性能差异仅为每千次调用的几分之一毫秒,可以忽略不计。但是,当大约一半的密码无效时,在“实例化”版本中抛出异常,它会慢三个数量级,这相当于每 1000 次调用大约多 1 秒。差异的大小当然会成倍增加,因为对表中的多个列进行了多次 CLR 调用,但这对于我的项目来说是 3 到 5 倍。那么,我是否可以接受每 1000 次更新额外 3 到 5 秒,作为保持我的代码非常简单和干净的权衡? 嗯,这取决于更新率。如果我的应用程序每秒获得 1000 次更新,那么 3 - 5 秒的延迟将是毁灭性的。另一方面,如果我每分钟或每小时获得 1000 次更新,那可能是完全可以接受的。 在我的情况下,我现在可以告诉你它是完全可以接受的,所以我想我会使用实例化方法,并允许错误通过。当然,在这个测试中,我处理了 CLR 中的错误,而不是让 SQL Server 处理它们。将错误信息编组到 SQL Server,然后可能会返回到应用程序,这肯定会使事情变得更慢。我想我必须完全实现它才能获得真正的测试,但从这个初步测试来看,我很确定结果会是什么。