在本文中,将探讨如何使用C#中的委托和泛型来创建一个灵活的验证器。委托是一种可以引用方法的类型,而泛型则允许创建可以操作任何类型的代码。将通过一个示例程序来展示如何动态地将函数添加到委托列表中,并根据验证结果来分组、延迟和改变函数的执行顺序。
这个程序是对之前文章中提到的程序的扩展,它包含一个单独的表单来展示这些新概念。程序本身并没有做太多事情,但可以在代码中设置断点来单元测试这些想法和概念。
当程序运行时,点击标有“Part 2”的按钮,将进入Form2,其中包含了本文的代码示例。
验证器包括两个基本类:
clsValidator:这是一个包含对Func
class clsValidator<T>
{
public Func<T, bool> CurrentValidator { get; set; }
public clsValidator<T> FailValidator { get; set; }
public clsValidator<T> PassValidator { get; set; }
}
DelegateValidator:它包含以下属性:
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; }
}
为了更清晰,在点击事件之外定义了所有的验证器作为单独的验证器。
为这个类创建的测试如下:
以下是一些定义的验证函数:
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
}
}
}
};