第十一节:特性(常见的特性标签、自定义特性、特性的使用案例)
一. 基本概念
1. 什么是特性?
MSDN官方给出的定义时:公共语言运行时允许添加类似关键字的描述声明,叫做特性,它对程序中的元素进行标注,如类型、字段、方法和属性等。Attribute和Microsoft .Net Framework文件的元数据(metadata)保存在一起,可以用来向运行时描述你的代码,或者在程序运行时影响程序的行为。
我的理解:在不影响类封装的情况的下,额外的添加一些信息,如果你用这个信息,则特性有效;如果你不用这个信息,那么这个特性无效。我们通常使用反射的方式获取类、属性、或方法等上面标注的特性。
2. 分清三个概念
(1). 注释:写在代码上面,一般用“ // ”和“ /**/ ”两个符号来表示,用来说明代码段或代码块的含义,方便自己或别人理解,对代码运行没有任何影响。
(2). 属性:属性和特性虽然一字之差,但是完全两个不同的概念,属性在面向对象中,提供了私有或字段的封装,可以通过get和set访问器来设置可读可写。
(3). 特性:不影响类的封装,在运行时,可以通过反射获取特性的内容。
3. DotNet中常用特性
(1). [Serializable]和[NonSerialized] :表示可以序列化或不可以序列化。
(2). [Obsolete("该类不能用了",true)]:表示该类(或属性或字段)将不能被使用。
(3). [AttributeUsage]:用来限制特性的作用范围、是否允许多次修饰同一个对象、是否允许被继承。
(4). [ReadOnly(true)]: 表示该特性作用于的属性为只读属性。
(5). [Description("XXX")]:用来描述作用对象含义的。
(6). [Flags]: 指示可以将枚举作为位域(即一组标志)处理
(7). [DllImport("")] : 使用包含要导入的方法的 DLL 的名称初始化
二. 自定义特性
1. 可作用的范围?
程序集(assembly)、模块(module)、类型(type)、属性(property)、事件(event)、字段(field)、方法(method)、参数(param)、返回值(return)。
2. 约定规则?
(1). 声明以Attribute结尾的类,即xxx+Attribute。
(2). 必须继承或间接继承Attribute类。
(3). 必须要有公有的构造函数。
3. 特性的使用方式(eg: ypfAttribute特性 mrAttribute特性)
(1). 可以省略后缀,也可以不省略。 eg:[ypfAttribute]、[ypf]、[ypfAttribute()]、[ypf()] 。
(2). 多个特性共同作用于一个对象的使用方式: [ypfAttribute][mrAttribute]、 [ypfAttribute,mrAttribute] (注:也可以省略后缀的各种组合形式)
4. 特性的构建
(1). 特性中除了可以声明构造函数,还可以声明属性和字段。(方法是不可以的)
(2). 可以通过DotNet自带的特性来限制自定义的特性。 [AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =false)]
a:AttributeTargets.All 表示可以加在所有的上面(包括类、属性、接口),也可以根据自己的需求,比如 AttributeTargets.Class 只允许加在类上。
b:约束该特性能否同时作用于某个元素(类、方法、属性等等)多次,默认为false。
c: 约束该特性作用于基类(或其它)上,其子类能否继承该特性,默认为true。
5. 下面自定义一个ypf特性
1 [AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =false)] 2 public class ypfAttribute : Attribute 3 { 4 /// <summary> 5 /// 默认的构造函数 6 /// </summary> 7 public ypfAttribute() 8 { 9 10 } 11 12 /// <summary> 13 /// 新声明的构造函数 14 /// </summary> 15 public ypfAttribute(int id) 16 { 17 18 } 19 /// <summary> 20 /// 新声明的构造函数 21 /// </summary> 22 public ypfAttribute(int id,string name) 23 { 24 25 } 26 /// <summary> 27 /// 声明要给属性 28 /// </summary> 29 public string Remark { get; set; } 30 31 /// <summary> 32 /// 声明一个字段 33 /// </summary> 34 public string Description = null; 35 }
(1). 作用形式
1 /// <summary> 2 /// 用户实体类 3 /// </summary> 5 [ypfAttribute] 6 [ypf] 7 [ypfAttribute()] 8 [ypf()] //以上四个等价 9 [ypf(123)] 10 [ypfAttribute(123)] 11 [ypf(123, "456")] 12 [ypfAttribute(123, "456")] 13 [ypf(Remark = "这里是特性")] 14 [ypf(123, Remark = "这里是特性")] 15 [ypfAttribute(123, Remark = "这里是特性")] 16 [ypf(123, "456", Remark = "这里是特性")] 17 [ypfAttribute(123, "456", Remark = "这里是特性", Description = "Description")] 18 19 [Table("User")] 20 public class UserModel 21 { 22 /// <summary> 23 /// 主键ID 24 /// </summary> 25 [myValidate(1, 1000)] 26 public int Id { get; set; } 27 /// <summary> 28 /// 账号 29 /// </summary> 30 public string Account { get; set; } 31 /// <summary> 32 /// 密码 33 /// </summary> 34 public string Password { get; set; } 35 /// <summary> 36 /// EMaill 37 /// </summary> 38 public string Email { get; set; } 39 40 }
(2). 如何获取特性中值?
( [ypfAttribute(123, "456", Remark = "这里是特性", Description = "Description")] )
A. 重新构建ypfAttribute中的内容,需要在该特性内部封装四个获取四个内容的方法
1 [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] 2 public class ypfAttribute : Attribute 3 { 4 public int _id; 5 public string _name; 6 7 /// <summary> 8 /// 默认的构造函数 9 /// </summary> 10 public ypfAttribute() 11 { 12 13 } 14 15 /// <summary> 16 /// 新声明的构造函数 17 /// </summary> 18 public ypfAttribute(int id) 19 { 20 21 } 22 /// <summary> 23 /// 新声明的构造函数 24 /// </summary> 25 public ypfAttribute(int id, string name) 26 { 27 this._id = id; 28 this._name = name; 29 } 30 /// <summary> 31 /// 声明要给属性 32 /// </summary> 33 public string Remark { get; set; } 34 35 /// <summary> 36 /// 声明一个字段 37 /// </summary> 38 public string Description = null; 39 40 //下面封装四个方法,分别获取该属性中的内容 41 public int GetOwnId() 42 { 43 return this._id; 44 } 45 public string GetOwnName() 46 { 47 return this._name; 48 } 49 public string GetOwnRemark() 50 { 51 return this.Remark; 52 } 53 public string GetOwnDescription() 54 { 55 return this.Description; 56 } 57 }
B:封装获取特性内容的可供外部调用的方法(在该方法中要调用ypf内部的封装的方法)
1 /// <summary> 2 /// 根据类型获取自定义特性ypfAttribute中的四个内容 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 /// <param name="t"></param> 6 public static void GetypfAttributeMsg<T>(this T t) where T : new() 7 { 8 //1.获取类 9 Type type = t.GetType(); 10 //2. 获取类中的所有特性 11 object[] attributeList = type.GetCustomAttributes(true); 12 //3. 查找ypf特性 13 foreach (var item in attributeList) 14 { 15 if (item is ypfAttribute) 16 { 17 ypfAttribute ypfattr = item as ypfAttribute; 18 int id = ypfattr.GetOwnId(); 19 string Name = ypfattr.GetOwnName(); 20 string remark = ypfattr.GetOwnRemark(); 21 string Des = ypfattr.GetOwnDescription(); 22 Console.WriteLine("ypfAttribute上的四个内容分别为:{0},{1},{2},{3}",id,Name,remark,Des); 23 } 24 } 25 }
C. 调用
1 { 2 //测试获取UserModel类上的ypfAttribute中的四个内容 3 Console.WriteLine("----------------------测试获取UserModel类上的ypfAttribute中的四个内容--------------------"); 4 UserModel userModel = new UserModel(); 5 userModel.GetypfAttributeMsg(); 6 }
D. 结果
三. 案例(制作一个验证属性长度的特性)
1. 构建一个myValidateAttribute特性,里面包含校验方法。
1 /// <summary> 2 /// 验证int类型属性长度的特性 3 /// </summary> 4 [AttributeUsage(AttributeTargets.Property)] //表示该特性只能作用于属性上 5 public class myValidateAttribute:Attribute 6 { 7 private int _min = 0; 8 private int _max = 0; 9 10 /// <summary> 11 /// 自定义构造函数 12 /// </summary> 13 /// <param name="min"></param> 14 /// <param name="max"></param> 15 public myValidateAttribute(int min,int max) 16 { 17 this._min = min; 18 this._max = max; 19 } 20 /// <summary> 21 /// 封装校验是否合法的方法 22 /// </summary> 23 /// <param name="num"></param> 24 /// <returns></returns> 25 public bool CheckIsRational(int num) 26 { 27 return num >= this._min && num <= this._max; 28 } 29 }
2. 外部校验方法
1 /// <summary> 2 /// 校验并保存的方法 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 /// <param name="t"></param> 6 public static void Save<T>(T t) 7 { 8 bool isSafe = true; 9 //1. 获取实例t所在的类 10 Type type = t.GetType(); 11 //2. type.GetProperties() 获取类中的所有属性 12 foreach (var property in type.GetProperties()) 13 { 14 //3. 获取该属性上的所有特性 15 object[] attributesList = property.GetCustomAttributes(true); 16 //4. 找属性中的特性 17 foreach (var item in attributesList) 18 { 19 if (item is myValidateAttribute) 20 { 21 myValidateAttribute attribute = item as myValidateAttribute; 22 //调用特性中的校验方法 23 //表示获取属性的值:property.GetValue(t) 24 isSafe = attribute.CheckIsRational((int)property.GetValue(t)); 25 } 26 } 27 if (!isSafe) 28 { 29 break; 30 } 31 } 32 if (isSafe) 33 { 34 Console.WriteLine("保存到数据库"); 35 } 36 else 37 { 38 Console.WriteLine("数据不合法"); 39 } 40 }
3. 调用
1 { 2 //制作一个可以限制int类型属性长度的特性,并封装对应的校验方法 3 UserModel userModel = new UserModel(); 4 // userModel.Id = 1000; 5 userModel.Id = 1001; // 不合法 6 BaseDal.Save<UserModel>(userModel); 7 }
4. 结果
四. 总结
自定义特性的使用步骤: 声明特性→特性中封装获取特性参数的方法→将特性作用于对象上→封装外部校验作用对象的方法→调用
封装外部校验作用对象的方法要用到反射,这里简单补充一下反射在知识:
反射详见章节: .Net进阶系列(2)-反射
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。