使用委托和泛型创建验证器

在本文中,将探讨如何使用C#中的委托泛型来创建一个灵活的验证器。委托是一种可以引用方法的类型,而泛型则允许创建可以操作任何类型的代码。将通过一个示例程序来展示如何动态地将函数添加到委托列表中,并根据验证结果来分组、延迟和改变函数的执行顺序。

这个程序是对之前文章中提到的程序的扩展,它包含一个单独的表单来展示这些新概念。程序本身并没有做太多事情,但可以在代码中设置断点来单元测试这些想法和概念。

当程序运行时,点击标有“Part 2”的按钮,将进入Form2,其中包含了本文的代码示例。

验证器包括两个基本类:

clsValidator:这是一个包含对Func委托CurrentValidator的引用,以及两个clsValidators的引用,FailValidator和PassValidator。如果CurrentValidator成功,将调用PassValidator,如果失败,则调用FailValidator。使用这个,可以创建一个基于true或false值遍历的节点树状结构。

class clsValidator<T> { public Func<T, bool> CurrentValidator { get; set; } public clsValidator<T> FailValidator { get; set; } public clsValidator<T> PassValidator { get; set; } }

DelegateValidator:它包含以下属性:

  • Value - T类型的值可以接受任何类型
  • ValidationRules - 包含clsValidator对象的根节点
  • Valid() - 一个布尔函数,用来使用ValidationRules来验证Value
class DelegateValidator<T> { public T Value { get; set; } public clsValidator<T> ValidationRules { get; set; } internal bool Valid() { // 用根节点初始化递归例程 return Valid(ValidationRules, Value); } // 这是一个递归调用,遍历clsValidator节点 private bool Valid(clsValidator<T> currval, T RecurseValue) { // 调用Func<T,bool>来测试Value bool success = currval.CurrentValidator.Invoke(RecurseValue); if (success) { if (currval.PassValidator != null) return Valid(currval.PassValidator, RecurseValue); } else { if (currval.FailValidator != null) return Valid(currval.FailValidator, RecurseValue); } return success; } }

使用代码

以下是如何测试整数值的示例:

private void btnExample1_Click(object sender, EventArgs e) { // 将测试整数值 DelegateValidator<int> dint = new DelegateValidator<int>(); bool retval; // 创建clsValidator的链表 // 当前值大于1,则下一个验证器(PassValidator)测试值是否也小于10 dint.ValidationRules = new clsValidator<int>() { CurrentValidator = (i) => { return i > 0; }, PassValidator = new clsValidator<int>() { CurrentValidator = (i) => { return i < 10; } } }; dint.Value = -1; retval = dint.Valid(); // False, 小于0 - 第一个验证器失败,不会去第二个验证器 dint.Value = 5; retval = dint.Valid(); // True, 大于0且小于10 - 两个验证器都返回true dint.Value = 50; retval = dint.Valid(); // False, 大于0且大于10 - 第一个验证器通过,第二个失败。 }

以下是代码的流程图:

注意,不需要为所有路径创建验证器,只需要想要进一步测试的路径。所以当第一个验证测试失败时,简单地从Valid()返回失败。

测试字符串值

以下是如何测试字符串值的示例,前半部分使用了示例一中显示的内联语法,后半部分展示了在点击事件范围之外定义的函数的使用。

private void btnExample2_Click(object sender, EventArgs e) { // 第一次测试 - 使用匿名函数定义验证器 DelegateValidator<string> delstr = new DelegateValidator<string>(); delstr.Value = "Steve"; // 内联验证 - 首先长度>1然后第一个字符是大写 delstr.ValidationRules = new clsValidator<string>() { CurrentValidator = (s) => { return s.Trim().Length > 1; }, PassValidator = new clsValidator<string>() { CurrentValidator = (s) => { return char.IsUpper(s, 0); } } }; bool isValid = delstr.Valid(); // 使用"Steve",验证通过。 // 第二次测试 - 使用与签名匹配的函数定义验证器 clsValidator<string> mv = new clsValidator<string>(); mv.CurrentValidator = LengthMinValidator; // 检查是否通过最小长度 mv.PassValidator = new clsValidator<string>(); // 如果通过 mv.PassValidator.CurrentValidator = LengthMaxValidator; // 检查是否最大长度 mv.PassValidator.PassValidator = new clsValidator<string>() { CurrentValidator = FirstLetterUpperValidator }; mv.PassValidator.FailValidator = new clsValidator<string>(); // 如果最大验证器失败,检查是否为数字 mv.PassValidator.FailValidator.CurrentValidator = IsNumberValidator; delstr.ValidationRules = mv; // 分配新规则 delstr.Value = "cat"; isValid = delstr.Valid(); // 应该为false,通过最小和最大长度但不是大写 delstr.Value = "10000"; isValid = delstr.Valid(); // 应该为true,长且为数字 delstr.Value = "10"; isValid = delstr.Valid(); // 应该为false,不长且不是大写 delstr.Value = "Cat"; isValid = delstr.Valid(); // 应该为true; }

以下是一些辅助函数的定义:

private bool LengthMinValidator(string s) { Console.WriteLine("LengthMinValidator"); return s.Length > 1; } private bool LengthMaxValidator(string s) { Console.WriteLine("LengthMaxValidator"); return s.Length < 5; } private bool FirstLetterUpperValidator(string s) { Console.WriteLine("FirstLetterUpperValidator"); return Char.IsUpper(s, 0); } private bool IsNumberValidator(string s) { Console.WriteLine("IsNumberValidator"); long j; return Int64.TryParse(s, out j); }

验证类

最后一个示例展示了一个可以被验证的类。它使用如下定义的类:

class clsEmployee { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public DateTime BirthDate { get; set; } public string Webpage { get; set; } }

为了更清晰,在点击事件之外定义了所有的验证器作为单独的验证器。

为这个类创建的测试如下:

  • 所有实例必须填写FirstName和LastName。
  • 如果实例包含Webpage,使用正则表达式进行验证。
  • 如果实例有大于零的年龄,根据输入的Birthdate进行确认。

以下是一些定义的验证函数:

private bool checkFirstName(clsEmployee e) { return e.FirstName != null && e.FirstName.Length > 0; } private bool checkLastName(clsEmployee e) { return e.LastName != null && e.LastName.Length > 0; } private bool checkWebAddress(clsEmployee e) { // 从谷歌搜索得到的正则表达式 Regex rg = new Regex(@"[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)"); if (string.IsNullOrEmpty(e.Webpage)) return true; if (e.Webpage.Trim().Length == 0) return true; // 不是错误 else return rg.IsMatch(e.Webpage); } private bool checkAge(clsEmployee e) { if (e.Age > 0) { if (DateTime.Now.Year - e.BirthDate.Year == e.Age) return true; return false; } else return true; // 不输入年龄也不是错误 }

以下是一些添加到示例中的人的样本。前两个将验证为true,后两个将验证为false。

clsEmployee ce1 = new clsEmployee() { FirstName = "Bob", LastName = "Marley" }; clsEmployee ce2 = new clsEmployee() { FirstName = "Steve", LastName = "Contos", Webpage = @"" }; // 网页和年龄都会失败 clsEmployee ce3 = new clsEmployee() { FirstName = "Bill", LastName = "Gates", Webpage = @"", Age = 10, BirthDate = Convert.ToDateTime("10/28/1955").Date }; // 因为没有姓而失败 clsEmployee ce4 = new clsEmployee() { FirstName = "Steve" };

最后,这是如何连接验证器的方式:

DelegateValidator<clsEmployee> dv = new DelegateValidator<clsEmployee>(); dv.ValidationRules = new clsValidator<clsEmployee>() { CurrentValidator = checkFirstName, PassValidator = new clsValidator<clsEmployee>() { // 第一个名字通过 CurrentValidator = checkLastName, PassValidator = new clsValidator<clsEmployee>() { // 姓通过 CurrentValidator = checkWebAddress, PassValidator = new clsValidator<clsEmployee>() { // 网络地址通过 CurrentValidator = checkAge } } } };
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485