C#高级编程--反射与特性
C#高级编程--反射与特性
特性attribute,特性是一种允许我们向程序集添加元数据的语言结构。特性是用于保存程序结构信息的特殊的类;
目标target,应用/添加了特性的程序结构(program construct)叫做目标;
消费者consumer,用来获取和使用元数据/特性的程序叫做特性的消费者;
.NET有很多内置特性,也可以自定义特性;将特性用于描述程序结构;
编译器获取源代码,并从特性产生关于源代码的描述信息(元数据),然后将元数据放到程序集中;
消费者程序读取特性/元数据,编译器既生成特性,同时也消费特性;
Type对象:
对于程序中用到的每一个类型,CLR都会创建一个包含这个类型的信息的Type对象;
不管该类型创建多少个实例,都只有一个Type对象,并且同时关联到该类及该类的所有实例;
获取Type对象的三种方法
分类 |
静态方法 |
关键字 |
实例方法 |
方法 |
Type.GetType() |
typeof() |
object.GetType() |
参数 |
string className |
Class |
无参 |
示例 |
Type t1 = Type.GetType("Person"); |
Type t2 = typeof(Person); |
Person p = new Person(); Type t3 = p.GetType(); |
错误示例 |
//t4 = Type.GetType(Person); //错误:静态方法不能传入类名 //t4 = Type.GetType(p); //错误:静态方法不能传入类对象 |
//t4 = typeof("Person"); //错误:不能传入类名 字符串 //t4 = typeof(p); //错误:不能传入类对象 |
|
//获取type对象的三种方法
//1、Type类的静态方法,只能传入类名字符串作为参数
Type t1 = Type.GetType("Person");
//2、使用typeof关键字,只能传入类名作为参数,而不能传入字符串或者类对象;
Type t2 = typeof(Person);
//3、使用object的实例方法,实例化的对象,调用基类object的GetType方法,无需参数;
Person p = new Person();
Type t3 = p.GetType();
//错误示例
Type t4;
//t4 = Type.GetType(Person); //错误:静态方法不能传入类名
//t4 = Type.GetType(p);//错误:静态方法不能传入类对象
//t4 = typeof("Person");//错误:typeof不能传入类名字符串
//t4 = typeof(p);//错误:typeof不能传入类对象
元数据metadata,有关程序及其类型的数据被称为元数据,它们保存在程序的程序集中;
反射reflection,程序在运行时,可以查看其他程序集或自身程序集的元数据。运行中程序查看元数据的行为叫做反射;
特性目标
All |
所有 |
可以对任何应用程序元素应用属性 |
Constructor |
构造函数 |
可以对构造函数应用属性 |
Method |
方法/函数 |
可以对方法应用属性 |
Property |
属性 |
可以对属性 (Property) 应用属性 (Attribute)。 |
Field |
字段 |
可以对字段应用属性 |
Parameter |
参数 |
可以对参数应用属性。 |
GenericParameter |
泛型参数 |
可以对泛型参数应用属性。 目前,此属性仅可应用于 C#、Microsoft 中间语言 (MSIL) 和已发出的代码中。 |
ReturnValue |
返回值 |
可以对返回值应用属性 |
Delegate |
委托 |
可以对委托应用属性 |
Event |
事件 |
可以对事件应用属性 |
Class |
类 |
可以对类应用属性 |
Struct |
结构体 |
可以对结构应用属性,即值类型。 |
Interface |
接口 |
可以对接口应用属性 |
Enum |
枚举 |
可以对枚举应用属性 |
Assembly |
程序集 |
可以对程序集应用属性 [assembly:MyAttribute(Parameters)] |
Module |
模块 |
可以对模块应用属性。 Module 引用的是可移植可执行文件(.dll 或 .exe),而不是 Visual Basic 标准模块。 [module:MyAttribute(Parameters)] |
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field),
AllowMultiple=false,Inherited=false]
public class FieldNameAtrribute:Attribute{
private string _fieldName;
public FieldNameAtrribute(string fieldName){_fieldName=fieldName;}
}
特性的特点:
- 特性可以用到那些程序元素上(特性目标AttributeTargets);
- 特性是否可以多次使用(AllowMultiple);
- 特性用到基类上或者接口上时,是否允许子类继承(Inherited);
- 特性需要那些参数(包括可选参数和必选参数);
特性分类(按用途):
- 用于编译器的特性(影响编译过程,如条件编译特性Conditional,过期标记特性Obsolete);
- 用于特性类的特性(指定自定义特性类的一些特点,如指定自定义特性用途的特性AttributeUsage);
- 用于标记元数据的自定义特性(为程序元素添加描述信息的元数据);
消费(查找使用)特性的本质:反射从程序集中读取元数据(特性),并实例化他们所表示的特性类。
特性分类(按程序元素)
- 全局特性,应用于程序集,必须显示指定;
- 局部特性,应用于其他程序元素(可以隐式或显示指定目标元素);
使用特性时的注意事项:
- 使用特性时,可以省略特性类名后的Attribute,当然也可以不省略,二者等价;
- 使用特性时,小括号中的参数有两类:位置参数和命名参数;
- 位置参数是指该特性类的构造函数中的参数;
- 命名参数并不是构造函数中的参数,而是该特性类中的公共字段/属性(这一点与普通类不同);
示例代码
#define condition1 #undef condition1 using System; using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; namespace ConsoleCore3 { class Program { static void Main(string[] args) { AttributeTest(); } static void AttributeTest() { Person p = new Person("tom"); //通过Person对象获取该类的Type对象 Type t = p.GetType(); //判断该程序元素上是否定义(添加)了某个特性 bool isDefined = t.IsDefined(typeof(SerializableAttribute)); Console.WriteLine($"SerializableAttribute defined:{isDefined}");//True //通过属性名获取属性的元数据(描述信息) PropertyInfo pInfo_Name = t.GetProperty("Name"); //通过反射获取属性类型 bool isString = pInfo_Name.PropertyType == typeof(String); Console.WriteLine($"pName is string:{isString}"); //通过反射获取属性值 Console.WriteLine($"Name={pInfo_Name.GetValue(p)}"); //通过反射为属性赋值 pInfo_Name.SetValue(p, "王英"); Console.WriteLine($"Name={p.Name}"); //通过反射获取属性上是否添加(定义)了某个特性 isDefined = pInfo_Name.IsDefined(typeof(FieldNameAttribute)); Console.WriteLine($"FieldNameAttribute defined:{isDefined}");//True //获取字段上添加的特性对象(元数据) FieldNameAttribute attr = pInfo_Name.GetCustomAttribute(typeof(FieldNameAttribute)) as FieldNameAttribute; //从读取的特性中获取元数据 Console.WriteLine($"FieldName={attr.FieldName},Comment={attr.Comment}"); //通过反射获取某个方法 MethodInfo mInfo_SayHi = t.GetMethod("SayHi"); //通过反射调用方法 mInfo_SayHi.Invoke(p, null); //通过反射获取某个方法--有参数 MethodInfo mInfo_Eat = t.GetMethod("Eat"); //调用有参方法 mInfo_Eat.Invoke(p, new object[] { "馒头" }); //输出结果 //SerializableAttribute defined:True //pName is string:True //Name = tom //Name = 王英 //FieldNameAttribute defined:True //FieldName = 姓名, Comment = 这是姓名属性 //hello world //馒头 真好吃 } static void UnsafeTest() { unsafe { // 错误 CS0208 无法获取托管类型(“Person”)的地址和大小,或者声明指向它的指针 //int size = sizeof(Person); int size = sizeof(double);//8 Console.WriteLine(size); Student s = new Student(); //错误 CS0208 无法获取托管类型(“Program.Student”)的地址和大小,或者声明指向它的指针 //Student* sp = & s; } var p = new Person(); //CS0233 “Person”没有预定义的大小,因此 sizeof 只能在不安全的上下文中使用 // int size = sizeof(Person) } unsafe struct Student { string name; int age; } static void AssemblyTest2() { const string className = "mynamespace.Calculator"; Assembly ass = Assembly.LoadFrom(@"D:\VSFile2019\ConsoleCore1\calculator\bin\Debug\netcoreapp3.1\calculator.dll"); //使用Invoke调用函数 //CreateInstance的参数string typeName,必须是 命名空间.类名 的格式; //typeName参数区分大小写 object cal = ass.CreateInstance(className); Console.WriteLine(cal); //calculator.Calculator //GetMethod的参数name区分大小写; //Invoke第一个参数是类的实例化对象,第二个参数是所调用的函数的参数列表,以object数组形式传递 //object[]中的元素个数必须和函数所需参数个数完全一致,否则报错; //object[]中的元素类型必须与参数与类型一致,或者能够隐式转换为目标类型,否则报错; object res = cal.GetType().GetMethod("Add").Invoke(cal, new object[] { 1.5, 'a' }); Console.WriteLine(res);//3.5 //使用动态类型调用函数 dynamic calculator = ass.CreateInstance(className); //动态类型没有智能提示,容易出现拼写错误 dynamic result = calculator.Add(2.2, 3.0); Console.WriteLine(result);//5.2 //调用一个不存在的函数,编译器不会报错,只有等到运行时才会出现异常; //:“'mynamespace.Calculator' does not contain a definition for 'Pow'” //calculator.Pow(2, 3); //大小写拼写错误,编译器不会有智能提示,只有等到运行时才会出现异常; //:“'mynamespace.Calculator' does not contain a definition for 'add'” calculator.add(5, 6); } static void AssemblyTest() { //通过dll名称 获取程序集 Assembly ass = Assembly.Load("itextsharp"); //itextsharp, Version=5.5.7.0, Culture=neutral, PublicKeyToken=8354ae6d2174ddca Console.WriteLine(ass); // Console.WriteLine("types--------------"); // ass.GetTypes().ToList().ForEach (c => Console.WriteLine(c)) ; Assembly ass2 = Assembly.LoadFile(@"D:\VSFile2019\仲裁文档标准化项目V1.0\AxInterop.DSOFramer.dll"); //AxInterop.DSOFramer, Version=2.2.0.0, Culture=neutral, PublicKeyToken=null Console.WriteLine(ass2); ass2.GetTypes().ToList().ForEach(c => Console.WriteLine(c)); Console.ReadLine(); } static void TypeTest2() { Type t = typeof(Person); Console.WriteLine(t.BaseType); //System.Object t.GetConstructors().ToList().ForEach(c => Console.WriteLine(c));//Void .ctor() Console.WriteLine("properties--------------"); t.GetProperties().ToList().ForEach(c => Console.WriteLine(c)); Console.WriteLine("fields--------------"); t.GetFields().ToList().ForEach(c => Console.WriteLine(c)); Console.WriteLine("members--------------"); t.GetMembers().ToList().ForEach(c => Console.WriteLine(c)); //System.Object //Void.ctor() //properties-------------- //Int32 Age //System.String Name //fields-------------- //Int32 id //members-------------- //Int32 get_Age() //Void set_Age(Int32) //System.String get_Name() //Void set_Name(System.String) //Void SayHi() //System.Type GetType() //System.String ToString() //Boolean Equals(System.Object) //Int32 GetHashCode() //Void.ctor() //Int32 Age //System.String Name //Int32 id } static void TestType() { //获取type对象的三种方法 //1、Type类的静态方法,只能传入类名字符串作为参数 Type t1 = Type.GetType("Person"); //2、使用typeof关键字,只能传入类名作为参数,而不能传入字符串或者类对象; Type t2 = typeof(Person); //3、使用object的实例方法,实例化的对象,调用基类object的GetType方法,无需参数; Person p = new Person(); Type t3 = p.GetType(); //错误示例 Type t4; //t4 = Type.GetType(Person); //错误:静态方法不能传入类名 //t4 = Type.GetType(p);//错误:静态方法不能传入类对象 //t4 = typeof("Person");//错误:typeof不能传入类名字符串 //t4 = typeof(p);//错误:typeof不能传入类对象 } //条件编译特性,该特性使用者为编译器 //因此只需在对应方法上标记该特性,而无需手动通过反射获取该特性 //如果定义了宏 condition1,则编译该方法,否则编译器忽略该方法 [Conditional("condition1")] static void TraceMessage(string s) { Console.WriteLine(s); } static void ConditionalTest() { TraceMessage("开始");//如果定义了宏#define condition1,编译器就编译本行代码,否则就忽略 Console.WriteLine("Hello World!"); TraceMessage("结束");//如果定义了宏#define condition1,编译器就编译本行代码,否则就忽略 } } [Serializable] class Person { // [FieldName("Age")] 该特性只能在属性上使用,而不能在字段上使用AttributeTargets.Property //如果想要在多种程序元素上使用,可以使用按位或运算符,连接各种目标元素 //AttributeTargets multiTargets = AttributeTargets.Field|AttributeTargets.Property|AttributeTargets.ReturnValue; public Person() { } public Person(string name) { Name = name; } public int id; [FieldName("Age")] // [FieldName("Age")] 该特性不能在同一个程序元素上重复使用 AllowMultiple = false public int Age { get; set; } [FieldName("姓名", Comment = "这是姓名属性")] public string Name { get; set; } public void SayHi() { Console.WriteLine("hello world"); } public void Eat(string food) { Console.WriteLine($"{food} 真好吃"); } } //用于描述特性用途的特性 // AttributeTargets特性目标,描述该特性可以用于哪些程序元素 //AllowMultiple 是否允许在同一个程序元素上添加多次 //Inherited 是否允许子类继承该特性 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public class FieldNameAttribute : Attribute { public string Comment { get; set; } private string _fieldName; public FieldNameAttribute(string fieldName) { _fieldName = fieldName; } public string FieldName { get => _fieldName; set => _fieldName = value; } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律