C#高级编程之特性
特性定义
MSDN的描述:使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。
参考此处作者的解释
https://www.cnblogs.com/chenxizhaolu/p/9497768.html
1.特性就是为了支持对象添加一些自我描述的信息,不影响类封装的前提添加额外信息。如果你用这个信息,那特性就有用;如果你不需要这个信息,那么这个特性就没用。
2.特性的基类:Attribute。例如:Obsolete特性,提出警告信息或错误信息,特性可以影响编译、影响运行。
3.特性类通常用Attribute结尾,在使用的时候可以用全称,也可以去掉这个结尾,也可以加上小括号显示调用构造函数,如果不加小括号默认调用无参构造函数,也可以在括号内直接给属性或字段赋值。
4.特性往往只能修饰一个对象一次,需要设置属性的属性的时候,需要给属性添加AttributeUsage属性,可以用来设置:是否允许多次修饰、修饰对象的类别(类or字段等)
5.DLL文件=IL中间语言+metadata元数据,特性信息会被编译到元数据中。我们可以通过使用ILSpy工具看到具体信息。
特性的使用
使用场景1:
这里以通过特性获取类或成员添加描述信息,然后在使用的时候拿到该信息为例:
第一步:定义一个特性类
public class CustomAttribute : Attribute { public int YearInfo { get; set; } public CustomAttribute() { Console.WriteLine("无参数构造函数"); } public CustomAttribute(int i) { YearInfo = i; Console.WriteLine("int 类型的构造函数"); } public void Show(string typeinfo) { Console.WriteLine("********************"); Console.WriteLine($"特性修饰的粒度{typeinfo}"); Console.WriteLine($"所在年信息为:{YearInfo}"); Console.WriteLine("通过反射调用特性中的方法"); } }
第二步:创建特性类实例,【里面包含着验证指定粒度的Model模型(类型、属性、方法)需要的数据,后面数据验证场景会讲到】
此处只是Model的不同粒度上附加数据。这里分别将特性附加于类型、属性和方法上。
public class Student { public string Id { get; set; } public string Name { get; set; } public void Answer(string message) { Console.WriteLine(message); } }
[Custom(2018)] class StudentVIP:Student { [Custom(2019)] public string VIPGroup { get; set; } [Custom(2020)] public void HomeWork() { Console.WriteLine("Homework"); } public long Salary { get; set; } }
通过ILSpy反编译工具可以看到:标记了特性的元素,都会在元素内部生成一个.custom,但是C#不能在元素内部调用
.class private auto ansi beforefieldinit LearningAttribute.StudentVIP extends LearningAttribute.Student { .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = ( 01 00 e2 07 00 00 00 00 ) // Fields .field private string '<VIPGroup>k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .field private int64 '<Salary>k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) // Methods .method public hidebysig specialname instance string get_VIPGroup () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2441 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld string LearningAttribute.StudentVIP::'<VIPGroup>k__BackingField' IL_0006: ret } // end of method StudentVIP::get_VIPGroup .method public hidebysig specialname instance void set_VIPGroup ( string 'value' ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2449 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stfld string LearningAttribute.StudentVIP::'<VIPGroup>k__BackingField' IL_0007: ret } // end of method StudentVIP::set_VIPGroup .method public hidebysig instance void HomeWork () cil managed { .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = ( 01 00 e4 07 00 00 00 00 ) // Method begins at RVA 0x2452 // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "Homework" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method StudentVIP::HomeWork .method public hidebysig specialname instance int64 get_Salary () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2460 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld int64 LearningAttribute.StudentVIP::'<Salary>k__BackingField' IL_0006: ret } // end of method StudentVIP::get_Salary .method public hidebysig specialname instance void set_Salary ( int64 'value' ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2468 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stfld int64 LearningAttribute.StudentVIP::'<Salary>k__BackingField' IL_0007: ret } // end of method StudentVIP::set_Salary .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x2471 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void LearningAttribute.Student::.ctor() IL_0006: nop IL_0007: ret } // end of method StudentVIP::.ctor // Properties .property instance string VIPGroup() { .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = ( 01 00 e3 07 00 00 00 00 ) .get instance string LearningAttribute.StudentVIP::get_VIPGroup() .set instance void LearningAttribute.StudentVIP::set_VIPGroup(string) } .property instance int64 Salary() { .get instance int64 LearningAttribute.StudentVIP::get_Salary() .set instance void LearningAttribute.StudentVIP::set_Salary(int64) } }
第三步:使用特性类实例。
将特性与程序实体相关联后,利用反射来获取附加在这些Model上的数据。一般是传入这个实体,然后利用反射判断其是否贴有数据,如果有,然后调用object[] aAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true);拿到这个特性的对象(其在这个环节进行了实例化),同时此时type又包含具体的数据信息,就可以将特性的附加信息与type进行互操作了。
Student student = new Student(); StudentVIP studentVIP = new StudentVIP() { Id = "1", Name = "HAHAH", VIPGroup = "Super学员" }; InvokeCenter.ManagerStudent<Student>(studentVIP);
public class InvokeCenter { public static void ManagerStudent<T>(T student) where T : Student { Console.WriteLine(student.Id); Console.WriteLine(student.Name); student.Answer("LNW"); Type type = student.GetType(); //这个type类级别上标注了特性 if (type.IsDefined(typeof(CustomAttribute), true))//先判断 { object[] aAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true);//此处为type foreach (CustomAttribute item in aAttributeArray) { item.Show(type.Name); } } //这个type属性上标注了特性 foreach (var prop in type.GetProperties())//先判断 { if (prop.IsDefined(typeof(CustomAttribute), true)) { object[] aAttributeArray = prop.GetCustomAttributes(typeof(CustomAttribute), true);//此处为prop foreach (CustomAttribute item in aAttributeArray) { item.Show(prop.Name); } } } //同理,方法上也可以获取 foreach (var method in type.GetMethods()) { if (method.IsDefined(typeof(CustomAttribute), true)) { object[] aMethodArray = method.GetCustomAttributes(typeof(CustomAttribute), true);//此处为method foreach (CustomAttribute item in aMethodArray) { item.Show(method.Name); } } } } }
结果为:
使用场景2
添加说明信息并获取,方便进行拓展。
比如我想根据不同的枚举状态显示不同的字符串信息。
定义枚举:
public enum UserStatus { /// <summary> /// 正常状态 /// </summary> Normal=0, /// <summary> /// 冻结状态 /// </summary> Frozen =1, /// <summary> /// 删除状态 /// </summary> Delted = 2 }
常规判断操作:
UserStatus userStatus = UserStatus.Normal; if (userStatus == UserStatus.Normal) { Console.WriteLine("正常状态"); } else if (userStatus == UserStatus.Frozen) { Console.WriteLine("冻结状态"); } else { Console.WriteLine("已删除"); }
//.....如果发生文字修改,那么改动量特别大,if else if 分支特别长。。
使用特性
public enum UserStatus { /// <summary> /// 正常状态 /// </summary> [Remark("正常状态")] Normal=0, /// <summary> /// 冻结状态 /// </summary> [Remark("冻结状态")] Frozen =1, /// <summary> /// 删除状态 /// </summary> [Remark("删除状态")] Delted = 2
//可以自由拓展。
}
//拓展方法 string remark3withExtendMethod = UserStatus.Delted.GetRemark();
public static class AttributeExtend
{
public static string GetRemark(this Enum value)//对比上面同方法
{ Type type = value.GetType(); var field = type.GetField(value.ToString());// if (field.IsDefined(typeof(RemarkAttribute), true)) { RemarkAttribute attribute = field.GetCustomAttribute(typeof(RemarkAttribute), true) as RemarkAttribute; return attribute.Remak; } else { return value.ToString(); } }
}
//其中GetFiled API含义如下:
// 摘要: // 搜索具有指定名称的公共字段。 // // 参数: // name: // 包含要获取的数据字段的名称的字符串。 // // 返回结果: // 如找到,则为表示具有指定名称的公共字段的对象;否则为 null。 // // 异常: // T:System.ArgumentNullException: // name 为 null。 // // T:System.NotSupportedException: // 此 System.Type 对象是尚未调用其 System.Reflection.Emit.TypeBuilder.CreateType 方法的 System.Reflection.Emit.TypeBuilder。 public FieldInfo GetField(string name);
使用场景3
做数据验证。
public class IntValidateAttribute : Attribute//特性名称约定俗成是以Attribute结尾,特性命名以具体功能名称为命名,此处就是整型数据验证 { /// <summary> /// 最小值 /// </summary> private int minValue { get; set; } /// <summary> /// 最大值 /// </summary> private int maxValue { get; set; } /// <summary> /// 构造函数 /// </summary> /// <param name="minValue"></param> /// <param name="maxValue"></param> public IntValidateAttribute(int minValue, int maxValue) { this.minValue = minValue; this.maxValue = maxValue; } /// <summary> /// 检验值是否合法 /// </summary> /// <param name="checkValue"></param> /// <returns></returns> public bool Validate(int checkValue) { return checkValue >= minValue && checkValue <= maxValue; } } public class User { [IntValidate(1, 10)] public int Id { get; set; } public string Name { get; set; } } public class BaseDal { public static string Insert<T>(T model) { Type modelType = typeof(T);//Model模型 //获取类型的所有属性 PropertyInfo[] propertyInfos = modelType.GetProperties(); bool boIsCheck = true; //循环所有属性 foreach (var property in propertyInfos) { //获取属性的所有特性 object[] attrs = property.GetCustomAttributes(true); if (property.PropertyType.Name.ToLower().Contains("int")) { foreach (var attr in attrs) { if (attr is IntValidateAttribute) { IntValidateAttribute intValidate = (IntValidateAttribute)attr;//拿到追加在Model上的特性实例化对象 //执行特性的验证逻辑 boIsCheck = intValidate.Validate((int)property.GetValue(model));//特性实例化对象intValidate执行对象方法Validate,同时proprty.GetValue(model)获取此时传进来的model实体的属性数据。进行数据验证。 } } } if (!boIsCheck) { break; } } if (boIsCheck) { return "验证通过,插入数据库成功"; } else { return "验证失败"; } } } class Program { public static void Main(string[] args) { string msg = BaseDal.Insert<User>(new User() { Id = 123, Name = "lvcc" });//传入Model User和User实体对象new User() { Id = 123, Name = "lvcc" }
Console.WriteLine(msg);
}
}
总结:
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。
特性具有以下属性:
- 特性向程序添加元数据。 元数据是程序中定义的类型的相关信息。 所有 .NET 程序集都包含一组指定的元数据,用于描述程序集中定义的类型和类型成员。 可以添加自定义特性来指定所需的其他任何信息。
- 可以将一个或多个特性应用于整个程序集、模块或较小的程序元素(如类和属性)。
- 特性可以像方法和属性一样接受自变量。
- 程序可使用反射来检查自己的元数据或其他程序中的元数据。 有关详细信息。
同时在MVC---EF--WCF--IOC 都有使用特性,无处不在。
参考资料:
https://www.cnblogs.com/chenxizhaolu/p/9497768.html
https://www.cnblogs.com/woadmin/p/9406970.html