前言:本文中有使用到扩展方法,可以参考博主关于扩展方法的笔记 https://www.cnblogs.com/JohnTang/p/10945696.html
特性的本质就是一个类,声明的时候,默认以Attribute结尾,直接或者是间接继承在Attribute抽象类。
如何使用特性:把这个特性以[]包裹标记在类或者是类内部的成员上,使用特性(不管是系统自带的特性,还是自定义特性,其实都是调用特性类的构造方法)
特性和注释的区别---看看特性的本质
1.注释只是会个描述;在编译后,是不存在的
2.特性呢?编译后是存在的
特性的应用场景: MVC 、webapi、IOC、ORM......可以说是无处不在
如何自定义特性,特性的多种标记
1.标记的特性,如果是以Attribute结尾,在标记时Attribute可以省略掉
2.可以标记的类内部的任何成员上
3.特性在标记的时候,其实就是去调用构造函数
4.在标记的时候也可以对公开的属性或者字段赋值
5.特性标记默认是不能重复标记的
6.AttributeUsage:也是一个特性,是用来修饰特性的特性--约束特性的特性
7.AttributeTargets指定当前特性只能标记在某个地方;建议大家在自己定义特性的时候,最好能够明确指定AttributeTargets--明确告诉别人,我这个特性是专们用来标记在哪里的
8.AllowMultiple:是否可以重复标记
9.Inherited:是否可以继承特性
创建自定义特性,并标记 示例:
1 创建自定义特性
/// <summary> /// 不管是自定义特性、还是系统中的特性 /// 都必须直接或间接继承自Attribute抽象类 /// </summary> public class CustomAttribute :Attribute { private int _Id { get; set; } public string _Name { get; set; } public int _Age; public CustomAttribute(int id) { this._Id = id; } public CustomAttribute(string name) { this._Name = name; } public void Do() { Console.WriteLine("this is CustomAttribute"); } }
2.标记特性
//自定义特性标记 /// <summary> /// 自定义特性标记 /// 特性就是调用特性类的构造方法 /// </summary> [Custom(112)] public int Id { get; set; } [Custom("茅斯李")] public string Name { get; set; }
疑问点,我们如果用student.custom是调用不到的,如何调用到特性内部的成员--自定义的这个特性,如何才能使用他呢?
1.进行反编译之后,在标记有特性的类的内部,生成了cutom的成员,但是我们不能直接调用;---要通过反射来调用的;要让特性生效,实际上来说是要去执行我们这个特性?---就需要构造特性的实例
特性标记后好像无法直接取调用他;好像没什么用;---当然是要用反射的,系统提供的特性可以使用是因为在封装好的底层代码里进行了处理,但是由于自定义的特性,系统无法知道需要什么样的规则
所以需要我们自己来指定规则。
2.在使用反射获取特性的时候,可以把标记在任何地方的特性都可以获取到
3.既然标记的特性可以通过反射来获取到实例,就可以加以应用
/// <summary> /// 通过反射来调用特性 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> public static void Show(Student student) { Type type = student.GetType(); var tt= (type.GetCustomAttributes(true)).GetType().Name; //获取当前Type下所有的属性上标记的特性 foreach (PropertyInfo prop in type.GetProperties()) { if (prop.IsDefined(typeof(CustomAttribute), true)) { //2.获取--先判断再获取--为了提高性能 foreach (CustomAttribute attribute in prop.GetCustomAttributes(true)) { Console.WriteLine($"attribute._Name:{attribute._Name}"); Console.WriteLine($"attribute._Age:{attribute._Age}"); attribute.Do(); } } } //获取当前Type下所有的字段上标记的特性 foreach (FieldInfo field in type.GetFields()) { if (field.IsDefined(typeof(CustomAttribute), true)) { //2.获取--先判断再获取--为了提高性能 foreach (CustomAttribute attribute in field.GetCustomAttributes(true)) { Console.WriteLine($"attribute._Name:{attribute._Name}"); Console.WriteLine($"attribute._Age:{attribute._Age}"); attribute.Do(); } } } //获取当前Type下所有的方法上标记的特性 foreach (MethodInfo method in type.GetMethods()) { foreach (ParameterInfo para in method.GetParameters()) { if (para.IsDefined(typeof(CustomAttribute), true)) { //2.获取--先判断再获取--为了提高性能 foreach (CustomAttribute attribute in para.GetCustomAttributes(true)) { Console.WriteLine($"attribute._Name:{attribute._Name}"); Console.WriteLine($"attribute._Age:{attribute._Age}"); attribute.Do(); } } } if (method.IsDefined(typeof(CustomAttribute), true)) { // 2.获取--先判断再获取--为了提高性能 foreach (CustomAttribute attribute in method.GetCustomAttributes(true)) { Console.WriteLine($"attribute._Name:{attribute._Name}"); Console.WriteLine($"attribute._Age:{attribute._Age}"); attribute.Do(); } } } } }
通过上述代码,我们可以获取到指定类上标记的所有特性(包含方法、字段、属性),那么我们会思考即使拿到了想要的信息,到底能给我们带来什么样的好处呢
场景:
比方说有一个用户信息,用户信息中有一个状态字段;在数据库中保存数据的时候,保存的是1,2,3 对应的就是这个枚举;数据库中保存的是数字,但是我们在查询到数据以后,展示给界面的时候,需要展示一个文字描述。
if (userState == UserStateEnum.Normal) { Console.WriteLine("正常状态"); } else if (userState == UserStateEnum.Frozen) { Console.WriteLine("已冻结"); }
这样的判断分支太多,而且如果枚举类发生更改,调用这个枚举类的所有类都需要更改,这样肯定不行。
我们可以通过特性获取描述信息信息----获取额外信息,
好处:
a.如果增加字段,就可以直接获取不用其他的改动
b.描述修改后,获取描述信息的方法不用修改
具体实现: 通过反射+特性+扩展方法,可以封装一个获取额外新的的公共方法
代码示例如下:
枚举
/// <summary> /// 比方说有一个用户信息,用户信息中有一个状态字段;在数据库中保存数据的时候,保存的是1,2,3 对应的就是这个枚举;数据库中保存的是数字,但是我们在查询到数据以后,展示给界面的时候,能展示数据吗?---需要展示一个文字描述 /// </summary> //[RemarkAttribute] public enum UserStateEnum { /// <summary> /// 正常状态---改成 “正常” /// </summary> [Remark("正常状态")] Normal = 1, /// <summary> /// 已冻结 /// </summary> [Remark("已冻结")] Frozen = 2, /// <summary> /// 已删除 /// </summary> [Remark("已删除")] Deleted = 3, [Remark("其他")] Other = 4, [Remark("Other1Other1Other1Other1Other1Other1Other1")] Other1 = 5 }
自定义特性类
public class RemarkAttribute :Attribute { private string? _Description; public RemarkAttribute(string description) { this._Description = description; } public string GetRemark() { return this._Description; } }
扩展方法
public static string GetRemark(this Enum @enum) { Type type = @enum.GetType(); string filedName = @enum.ToString(); FieldInfo fieldInfo = type.GetField(filedName); if (fieldInfo.IsDefined(typeof(RemarkAttribute),true)) { RemarkAttribute attribute = fieldInfo.GetCustomAttribute<RemarkAttribute>(); string description = attribute.GetRemark(); return description; } return @enum.ToString(); }
UserStateEnum normal = UserStateEnum.Normal;
string strnormalRemark = RemarkAttributeExtension.GetRemark(normal);
完成调用,这样,不管枚举里如何修改,对于调用方而言都不用做任何更改
需求进一步升级:
1.如果要保存一条数据到数据库中去
2.从前端提交过来的数据格式为:
{
"id": 0,
"name": "string",
"age": 0,
"state": 1
}
3.包含了很多字;
4.如果数据库中Name的值要求存储的长度为40个字符--如果保存的数据超过40个字符---肯定会报错
5.肯定要在保存之前就需要验证这行数据
//模拟数据 UserInfo adduse = new UserInfo() { Id = 123, Name = "456464", Age = 25, Mobile = "sdfsdf" }; //传统方式如下,缺点:判断分支太多,代码量很大 if (adduse.Name == null) { Console.WriteLine("不能为空"); } if (adduse.Name.Length > 40) { Console.WriteLine("Name超长了"); } if (adduse.Mobile.Length != 11) { Console.WriteLine("手机号有问题"); }
验证思路概述:
增加了一个特性:可以对一个实体中的字段做验证 - 和验证不能为空
1.通过特性加反射额外的获取了一个功能
2.实体验证 == 特性获取额外信息 + 特性获取额外的来完成的
好处:
1.只要是把验证的规则特性定义好,就可以重新使用
2.如果需要验证哪个属性,就把特性标记在哪个属性上就可以了;
3.只是标记了一个特性,就可以获取了一个验证的逻辑
具体实现:
1.创建一个抽象类 继承 Attribute,定义用于验证的抽象方法
2.创建一个验证类,继承自抽象类,重写用于验证的抽象方法,并定义公开的验证返回信息
3创建反射验证类(静态类),这里是用于最上层调用验证,通过反射获取到信息后调用验证类里的验证方法,并将验证的值返回。
抽象类Code
public abstract class AbstractAttribute : Attribute { public abstract ApiResult Validate(object oValue); }
验证类Code
/// <summary> /// AbstractAttribute 中继承了Attribute /// </summary> public class RequiredTestAttribute : AbstractAttribute { public string _ErrorMessage; public RequiredTestAttribute(string errorMessage) { this._ErrorMessage = errorMessage; } /// <summary> /// 重写AbstractAttribute类的抽象方法 /// </summary> /// <param name="value"></param> /// <returns></returns> public override ApiResult Validate(object value) { //if (string.IsNullOrWhiteSpace(value) //{ // return false; //} //else //{ // return true; //} bool bResult = value != null && !string.IsNullOrWhiteSpace(value.ToString()); if (bResult) { return new ApiResult() { Success = bResult }; } else { return new ApiResult() { Success = bResult, ErrorMessage = _ErrorMessage }; } } }
反射验证类(供上端调用)Code
public static ApiResult ValiDate<T>(this T t) { Type type = t.GetType(); //遍历传递过来的类型的属性 foreach (PropertyInfo prp in type.GetProperties()) { //判断属性上是否有RequiredTestAttribute if (prp.IsDefined(typeof(RequiredTestAttribute), true)) { RequiredTestAttribute attribute = prp.GetCustomAttribute<RequiredTestAttribute>(); object ovalue = prp.GetValue(t); ApiResult result = attribute.Validate(ovalue); if (result.Success == false) { return result; } } } return new ApiResult() { Success = true }; }
标签:attribute,Name,标记,Attribute,特性,详解,._,public From: https://www.cnblogs.com/JohnTang/p/17048278.html