一、特性(Attribute)定义
特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。
特性使用中括号声明,特性是一个类且必须直接或者间接继承 Attribute。 一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。
1.创建一个自定义特性
代码如下(示例):
public class CustomAttribute : Attribute {
}
2.使用
代码如下(示例):
[Custom]
public class Student {
}
1.自定义的Attribute必须直接或者间接继承System.Attribute。
2.有一个约定:所有自定义的特性名称都应该有个Attribute后缀。因为当你的Attribute施加到一个程序的元素上的时候,编译器先查找你的Attribute的定义,如果没有找到,那么它就会查找“Attribute名称"+Attribute的定义。
二、Attribute关联的元素
程序集(assembly)、模块(module)、类型(type)、属性(property)、事件(event)、字段(field)、方法(method)、参数(param)、返回值(return)。
1.先创建一个 CustomAttribute 自定义特性
代码如下(示例):
public class CustomAttribute : Attribute {
public string Desc { get; set; }
public CustomAttribute()
{
Console.WriteLine("CustomAttribute构造函数");
}
public CustomAttribute(string desc) {
this.Desc = desc;
Console.WriteLine("CustomAttribute有参构造函数");
}
}
2.创建Student 使用特性
代码如下(示例):
[Custom("我在类上使用")]
public class Student {
[Custom("我在字段上使用")]
public string name;
[Custom("我在属性上使用")]
public string Name { get { return name; } set { name = value; } }
[Custom("我在方法上使用")]
[return: Custom("我在返回值上")]
public string GetName([Custom("参数")] int Id) {
return name;
}
}
2.如何使用
代码如下(示例):
internal class Program
{
static void Main(string[] args)
{
Student student=new Student() { Name="小明"};
Console.WriteLine(student.GetName(1));
Console.ReadKey();
}
}
调试结果
从调试结果来看 跟不加特性没什么区别,那怎么体现出用到的特性呢
static void Main(string[] args)
{
//Student student=new Student() { Name="小明"};
//Console.WriteLine(student.GetName(1));
Type type = typeof(Student);
//判断是否在类上使用特性
if (type.IsDefined(typeof(CustomAttribute), true))
{
CustomAttribute customAttribute = (CustomAttribute)type.GetCustomAttribute(typeof(CustomAttribute), true);
Console.WriteLine(customAttribute.Desc);
}
MethodInfo method = type.GetMethod("GetName");
//判断是否在方法上使用特性
if (method.IsDefined(typeof(CustomAttribute), true))
{
CustomAttribute customAttribute = (CustomAttribute)method.GetCustomAttribute(typeof(CustomAttribute), true);
Console.WriteLine(customAttribute.Desc);
}
ParameterInfo parameter = method.GetParameters()[0];
//判断是否在参数上使用特性
if (parameter.IsDefined(typeof(CustomAttribute), true))
{
CustomAttribute customAttribute = (CustomAttribute)parameter.GetCustomAttributes(typeof(CustomAttribute), true)[0];
Console.WriteLine(customAttribute.Desc);
}
ParameterInfo returnParameter = method.ReturnParameter;
//判断是否在方法的返回值上使用特性
if (returnParameter.IsDefined(typeof(CustomAttribute), true))
{
CustomAttribute customAttribute = (CustomAttribute)returnParameter.GetCustomAttribute(typeof(CustomAttribute), true);
Console.WriteLine(customAttribute.Desc);
}
PropertyInfo property = type.GetProperty("Name");
//判断是否在属性上使用特性
if (property.IsDefined(typeof(CustomAttribute), true))
{
CustomAttribute customAttribute = (CustomAttribute)property.GetCustomAttribute(typeof(CustomAttribute), true);
Console.WriteLine(customAttribute.Desc);
}
FieldInfo field = type.GetField("name");
//判断是否在字段上使用特性
if (field.IsDefined(typeof(CustomAttribute), true))
{
CustomAttribute customAttribute = (CustomAttribute)field.GetCustomAttribute(typeof(CustomAttribute), true);
Console.WriteLine(customAttribute.Desc);
}
Console.ReadKey();
}
调试结果
可以发现 ,并没有对Student类进行初始化,但是我们在Student使用到特性的地方,取到的Attribute信息
3.总结
Attribute类是在编译的时候被实例化的,而不是像通常的类那样在运行时候才实例化。
三、Attribute的运用
使用场景:某些时候我们在程序处理过程中为了程序更加健全及安全需要校验一些参数的合法性,比如邮件格式校验、用户的年龄等类似的需求,以下以邮件合法及用户年龄的范围只能在1~100范围为例。刚开始我们最直接也最先想到的就是传统的方式写参数校验。如下所示:
internal class Program
{
static void Main(string[] args)
{
User user = new User();
if (string.IsNullOrWhiteSpace(user.Email)) //:这里只判断了邮箱为空,省略了邮箱合法性判断
{
Console.WriteLine("Email 参数不符合!");
//:这里不执行保存,提示用户参数不合法
}
if (user.Age < 100 || user.Age > 0)
{
Console.WriteLine("Age 参数不符合,Age范围只能是0~100");
//:这里不执行保存,提示用户参数不合法
}
Console.ReadKey();
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
这种会造成代码重复,且维护困难
第二种:由于第一种产生的问题,有的小伙伴会将验证逻辑放到 get set 访问器中,这种造成了职责不分明,实体本身是承载信息的,不需要存在业务逻辑
public class User
{
public string Name { get; set; }
private int age;
public int Age { get {
return this.age;
} set {
if (value >=0 && value <=100)
{
this.age = value;
}
else
{
throw new Exception("Age不合法");
}
} }
public string Email { get; set; }
}
特性方式:由于以上两种产生的问题,我们可以使用特性进行处理,易于维护、易于编码、易于公用
1.定义一个抽象类,继承自Attribute
public abstract class AbstractValidateAttribute : Attribute
{
public abstract bool Validate(object oValue);
}
2.定义一个 LongAttribute
继承 AbstractValidateAttribute,其中有个最小、最大字段, 重写Validate 方法用于处理效验逻辑
public class LongAttribute : AbstractValidateAttribute
{
private int min { get; set; }
private int max { get; set; }
public LongAttribute(int min, int max)
{
this.min = min;
this.max = max;
}
public override bool Validate(object oValue)
{
return oValue != null && int.TryParse(oValue.ToString(), out int num) && num >= this.min && num <= this.max;
}
}
3.定义一个用户表 User ,标上LongAttribute特性
继承 AbstractValidateAttribute,其中有个最小、最大字段, 重写Validate 方法用于处理效验逻辑
public class User
{
public string Name { get; set; }
[Long(1, 100)]
public int Age
{
get; set;
}
}
4.定义一个 Validate 的扩张类与扩张方法
其作用是取得对象 Type 获取 LongAttribute 并调用其 Validate 进行业务逻辑效验
public static class ValidateExtension
{
public static bool Validate(this object val)
{
Type type = val.GetType();
foreach (var prop in type.GetProperties())
{
if (prop.IsDefined(typeof(LongAttribute), true))
{
LongAttribute longAttribute = (LongAttribute)prop.GetCustomAttribute(typeof(LongAttribute), true);
if (!longAttribute.Validate(prop.GetValue(val)))
{
return false;
}
}
}
return true;
}
}
5.实例化 User
分别实例化两个 User,并对Age字段赋予不合法与合法的值
static void Main(string[] args)
{
User user1 = new User();
user1.Age = -1;
User user2 = new User();
user2.Age = 23;
Console.WriteLine(user1.Validate());
Console.ReadKey();
}
6.总结
后期如果新增不同参数类型的校验,只需要新增对应的类,继承自AbstractValidateAttribute ,在新增的类中实现具体的校验逻辑,并且在需要校验的属性上标记对应的特性即可,方便代码扩展