• C#进阶07——反射和特性


    1.反射

    1.什么是程序集

    程序集是经由百年一起编译得到的,供进一步编译执行的那个中间产物

    在WINDOWS系统中,它一般表现为后缀.dll(库文件)或者是.exe(可执行文件)的格式

    说人话:

    程序集就是我们写的一个代码集合,我们现在写的所有代码

    最终都会被编译器翻译为一个程序集供别人使用

    比如一个代码库文件(dll)或者一个可执行文件(exe)

    2.元数据

    元数据就是用来描述数据的数据

    这个概念不仅仅用于程序上,在别的领域也有元数据

    说人话:

    程序中的类,类中的函数、变量等等信息就是 程序的元数据

    有关程序以及类型的数据被称为 元数据,它们保存在程序集中

    3.反射的概念

    程序在运行时,可以查看其他程序集或者自身的元数据

    一个运行的程序查看本身或者其它程序的元数据的行为就叫做反射

    说人话:

    在程序运行时。通过反射可以得到其它程序集或者自己程序集代码的各种信息

    类,函数,变量,对象等等,实例化它们,执行它们,操作它们

    4.反射的作用

    因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性

    1. 程序运行时得到所有元数据,包括元数据的特性
    2. 程序运行时,实例化对象,操作对象
    3. 程序运行时创建新对象,用这些对象执行任务

    5.语法相关 

    1. class Test
    2. {
    3. private int i = 1;
    4. public int j = 0;
    5. public string str = "123";
    6. public Test()
    7. {
    8. }
    9. public Test(int i)
    10. {
    11. this.i = i;
    12. }
    13. public Test(int i, string str) : this(i)
    14. {
    15. this.str = str;
    16. }
    17. public void Speak()
    18. {
    19. Console.WriteLine(i);
    20. }
    21. }

    1.type 

    2.Assembly

    3.Activator

     6.反射关键类Type

    Type(类的信息类)

    它是反射功能的基础!

    它是访问元数据的主要方式

    使用 Type 的成员获取有关类型申明的信息

    有关类型的成员(如构造函数,方法,字段,属性和类的事件)

    1.获取Type

    1.万物之父object中的 GetType()可以获取对象的Type

    1. int a = 42;
    2. Type type = a.GetType();
    3. Console.WriteLine(type);

    2.通过typeof关键字 传入类名 也可以得到对象的Type

    1. Type type2 = typeof(int);
    2. Console.WriteLine(type2);

    3.通过类的名字 也可以获取类型

       注意 类名必须包含命名空间 不然找不到

    1. Type type3 = Type.GetType("System.Int32");
    2. Console.WriteLine(type3);

    2.得到类的程序集信息

    可以通过Type可以得到类型所在程序集信息

    1. Console.WriteLine(type.Assembly);
    2. Console.WriteLine(type2.Assembly);
    3. Console.WriteLine(type3.Assembly);

    3.获取类中的所有公共成员

    1. //首先得到Type
    2. Type t = typeof(Test);
    3. //然后得到所有公共成员
    4. //需要引用命名空间 using System.Reflection;
    5. MemberInfo[] infos = t.GetMembers();
    6. for (int i = 0; i < infos.Length; i++)
    7. {
    8. Console.WriteLine(infos[i]);
    9. }

     4.获取类的公共构造函数并调用

    1.获取所有构造函数

    1. ConstructorInfo[] ctors = t.GetConstructors();
    2. for (int i = 0; i < ctors.Length; i++)
    3. {
    4. Console.WriteLine(ctors[i]);
    5. }

    2.获取其中一个构造函数 并执行

    得构造函数传入 Type数组 数组中内容按顺序是参数类型

    执行构造函数传入 object数组 表示按顺序传入的参数

    1. // 2-1 得到无参构造
    2. ConstructorInfo info = t.GetConstructor(new Type[0]);
    3. //执行无参构造 无参构造 没有参数 传null
    4. Test obj = info.Invoke(null) as Test;
    5. Console.WriteLine(obj.j);
    6. // 2-2 得到有参构造
    7. ConstructorInfo info2 = t.GetConstructor(new Type[] { typeof(int) });
    8. obj = info2.Invoke(new object[] { 2 }) as Test;
    9. Console.WriteLine(obj.str);
    10. ConstructorInfo info3 = t.GetConstructor(new Type[] { typeof(int), typeof(string) });
    11. obj = info3.Invoke(new object[] { 4, "4444" }) as Test;

    5.获取类的公共成员变量 

    1.得到所有成员变量

    1. FieldInfo[] fieldInfos = t.GetFields();
    2. for (int i = 0; i < fieldInfos.Length; i++)
    3. {
    4. Console.WriteLine(fieldInfos[i]);
    5. }

     2.得到指定名称的公共成员变量

    1. FieldInfo infoj = t.GetField("j");
    2. Console.WriteLine(infoj);

    3.通过反射获取和设置对象的值

    1. Test test = new Test();
    2. test.j = 99;
    3. test.str = "2222";
    4. //3-1通过反射 获取对象的某个变量的值
    5. Console.WriteLine(infoj.GetValue(test));
    6. //3-2通过反射 设置指定对象的某个变量的值
    7. infoj.SetValue(test, 100);
    8. Console.WriteLine(infoj.GetValue(test));

    6.获取类的公共成员方法 

    1. //通过Type类中的 GetMethod方法 得到类中的方法
    2. //MethodInfo 是方法的反射信息
    3. Type strType = typeof(string);
    4. MethodInfo[] methods = strType.GetMethods();
    5. for (int i = 0; i < methods.Length; i++)
    6. {
    7. Console.WriteLine(methods[i]);
    8. }
    9. //1.如果存在方法重载 用Type数组表示参数类型
    10. MethodInfo subStr = strType.GetMethod("Substring",new Type[] { typeof(int),typeof(int)});
    11. //2.调用该方法
    12. //注意:如果是静态方法 Invoke中的第一个参数传null即可
    13. string str = "Hello,World!";
    14. object result=subStr.Invoke(str,new object[] {7,5});
    15. Console.WriteLine(result);

    7.其他 

    1. //Type
    2. //得枚举
    3. //GetEnumName
    4. //GetEnumNames
    5. //得事件
    6. //GetEvent
    7. //GetEvents
    8. //得接口
    9. //GetInterface
    10. //GetInterfaces
    11. //得属性
    12. //GetProperty
    13. //GetPropertys
    14. //等等

    7.反射关键类Assembly

    程序集类

    主要用来加载其他程序集,加载后才能使用Type来使用其他程序集中的信息

    如果想要使用不是自己程序集中的内容 需要先加载程序集

    比如 dll文件(库文件)
    简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类

    三种加载程序集的函数
    一般用来加载在同一文件下的其它程序集
    Assembly asembly2 = Assembly.Load("程序集名称");

    一般用来加载不在同一文件下的其它程序集
    Assembly asembly = Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
    Assembly asembly3 = Assembly.LoadFile("要加载的文件的完全限定路径");

    1.先加载一个指定程序集 

    1. Assembly assembly = Assembly.LoadFrom(@"D:\C#\CSharp进阶\lesson18-多线程\bin\Debug\netcoreapp3.1\lesson18-多线程");
    2. Type[] types = assembly.GetTypes();
    3. for (int i = 0; i < types.Length; i++)
    4. {
    5. Console.WriteLine(types[i]);
    6. }

     2.再加载程序集中的一个类对象 之后才能使用反射

    1. Type icon = assembly.GetType("lesson18-多线程.Icon");
    2. MemberInfo[] members = icon.GetMembers();
    3. for (int i = 0; i < members.Length; i++)
    4. {
    5. Console.WriteLine(members[i]);
    6. }
    7. //通过反射 实例化一个 icon对象
    8. //首先得到枚举Type 来得到可以传入的参数
    9. Type moveDir = assembly.GetType("lesson18-多线程.E_MoveDir");
    10. FieldInfo right = moveDir.GetField("Right");
    11. //直接实例化对象
    12. Object iconObj = Activator.CreateInstance(icon, 10, 5, right.GetValue(null));
    13. //得到对象中的方法 通过反射
    14. MethodInfo move = icon.GetMethod("Move");
    15. MethodInfo draw = icon.GetMethod("Draw");
    16. MethodInfo clear = icon.GetMethod("Clear");
    17. Console.Clear();
    18. while (true)
    19. {
    20. Thread.Sleep(1000);
    21. clear.Invoke(iconObj, null);
    22. draw.Invoke(iconObj, null);
    23. move.Invoke(iconObj, null);
    24. }

    3.类库工程创建 

    8.反射关键类Activator

    用于快速实例化对象的类

    用于将Type对象快捷实例化为对象

    先得到Type   然后   快速实例化一个对象

    Type testType = typeof(Test);

    1.无参构造

    1. Test testObj = Activator.CreateInstance(testType) as Test;
    2. Console.WriteLine(testObj.str);

    2.有参构造

    1. testObj = Activator.CreateInstance(testType, 99) as Test;
    2. Console.WriteLine(testObj.j);
    3. testObj = Activator.CreateInstance(testType, 55, "111222") as Test;
    4. Console.WriteLine(testObj.j);

    9.总结 

    反射

    在程序运行时,通过反射可以得到其他程序集或者自己的程序集代码的各种信息

    类、函数、变量、对象等等,实例他们,执行他们,操作他们

    关键类:Type、Assembly、Activator

    对于我们的意义

    在初中级阶段 基本不会使用反射

    所有目前对于大家来说,了解反射可以做申明就行

    很长时间都不会用到反射相关知识点

    为什么要学反射

    为了之后学习Unity引擎的基本工作原理做铺垫

    Unity引擎的基本工作机制 就是建立在反射的基础上

    2.特性

    1.特性是什么

    特性是一种允许我们向程序的程序集添加元数据的语言结构

    它是用于保存程序结构信息的某种特殊类型的类

    特性提供功能强大的方法以将申明信息与 C# 代码(类型、方法、属性等)相关联

    特性与程序实体关联后,即可在运行时使用反射查询特性信息

    特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中

    它可以放置在几乎所有的申明中(类、变量、函数等等申明)

    说人话:

    特性本质是个类

    我们可以利用特性类为元数据添加额外信息

    比如一个类、成员变量、成员方法等等为他们添加更多的额外信息

    之后可以通过反射来获取这些额外信息

    2.自定义特性

    1. //继承特性基类 Attribute
    2. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field,AllowMultiple =true,Inherited =false)]
    3. class MyCustomAttribute : Attribute
    4. {
    5. //特性中的成员 一般根据需求来写
    6. public string info;
    7. public MyCustomAttribute(string info)
    8. {
    9. this.info = info;
    10. }
    11. public void TestFun()
    12. {
    13. Console.WriteLine("特性的方法");
    14. }
    15. }

    3.特性的使用

    基本语法

    [特性名(参数列表)]

    本质上 就是在调用特性类的构造函数

    写在哪里?

    类、函数、变量上一行,表示他们具有该特性信息

    1. [MyCustom("自己写的用于计算的类")]
    2. [MyCustom("自己写的用于计算的类")]
    3. class MyClass
    4. {
    5. [MyCustom("这是一个成员变量")]
    6. public int value;
    7. //[MyCustom("这是一个用于计算加法的函数")]
    8. //public void TestFun([MyCustom("函数参数")] int a)
    9. //{
    10. //}
    11. public void TestFun(int a)
    12. {
    13. }
    14. }
    1. class Program
    2. {
    3. [DllImport("Test.dll")]
    4. public static extern int Add(int a, int b);
    5. [Conditional("Fun")]
    6. static void Fun()
    7. {
    8. Console.WriteLine("Fun执行");
    9. }
    10. static void Main(string[] args)
    11. {
    12. Console.WriteLine("特性");
    13. #region 特性的使用
    14. MyClass mc = new MyClass();
    15. Type t = mc.GetType();
    16. //t = typeof(MyClass);
    17. //t = Type.GetType("lesson21-特性。MyClass");
    18. //判断是否使用了某个特性
    19. //参数一:代表特性的类型
    20. //参数二:代表是否搜索继承链(属性和事件忽略此参数)
    21. if (t.IsDefined(typeof(MyCustomAttribute), false))
    22. {
    23. Console.WriteLine("该类型应用了MyCustom特性");
    24. }
    25. object[]array=t.GetCustomAttributes(true);
    26. for (int i = 0; i < array.Length; i++)
    27. {
    28. if (array[i] is MyCustomAttribute)
    29. {
    30. Console.WriteLine((array[i] as MyCustomAttribute).info);
    31. (array[i] as MyCustomAttribute).TestFun();
    32. }
    33. }
    34. TestClass tc = new TestClass();
    35. //tc.OldSpeak("123");
    36. tc.Speak();
    37. tc.SpeakCaller("123123123");
    38. Fun();
    39. #endregion
    40. }
    41. }

     4.限制自定义特性的使用范围

    1. //通过为特性类 加特性 限制其使用范围
    2. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
    3. //参数一:AttributeTargets 特性能够用在哪些地方
    4. //参数二:AllowMultiple 是否允许多个特性实例用在同一个目标上
    5. //参数三:Inherited 特性是否能被派生类和重写成员继承
    6. public class MyCustom2Attribute : Attribute
    7. {
    8. }

    5.系统自带特性——过时特性

    过时特性    Obsolete

    用于提示用户 使用的方法等成员已经过时 建议使用新方法

    一般加载函数前的特性

    1. class TestClass
    2. {
    3. //参数一:调用过时方法时 提示的内容
    4. //参数二:true-使用该方法时会报错 false-使用该方法时直接警告
    5. [Obsolete("OldSpeak方法已经过时了,请使用Speak方法",false)]
    6. public void OldSpeak(string str)
    7. {
    8. Console.WriteLine(str);
    9. }
    10. public void Speak()
    11. {
    12. Console.WriteLine("123");
    13. }
    14. public void SpeakCaller(string str, [CallerFilePath]string fileName = "",
    15. [CallerLineNumber]int line = 0, [CallerMemberName]string target = "")
    16. {
    17. Console.WriteLine(str);
    18. Console.WriteLine(fileName);
    19. Console.WriteLine(line);
    20. Console.WriteLine(target);
    21. }
    22. }

    6.系统自带特性——调用者信息特性

    哪个文件调用

    CallerFilePath特性
    哪一行调用
    CallerLineNumber特性
    哪个函数调用
    CallerMemberName特性
     
    需要引用命名空间 using System.Runtime.CompilerServices;
    一般作为函数参数的特性

    7.系统自带特性——条件编译特性

    条件编译特性
    Conditional
    它会和预处理指令 #define 配合使用
     
    需要引用命名空间 using System.Diagnostics;
    主要可以用在一些调试代码上
    有时想执行有时不想执行的代码
    #define 函数名
    函数前面加 [Conditional("函数名")]

    8.系统自带特性——外部Dll包函数特性 

     DllImport
     
    用来标记非.Net(C#)的函数,表明该函数在一个外部的DLL中定义
    一般用来调用C或者C++的Dll包写好的方法
    需要引用命名空间 using System.Runtime.InteropServices;
     
    函数前面加 [DllImport("函数名")]

    9.总结

    特性是用于 为元数据再添加更多的额外信息(变量、方法等等)

    我们可以通过反射获取这些额外的数据 来进行一些特殊的处理

    自定义特性——继承Attribute

    系统自带特性:过时特性

    为什么要学特性

    Unity引擎中很多地方都用到了特性来进行一些特殊处理

  • 相关阅读:
    【高级篇 / ZTNA】(7.0) ❀ 02. 测试版许可注册冲突的解决办法 ❀ FortiGate 防火墙
    2022-05-05 mybatis-plus 批量插入修改操作
    报错解决:RuntimeError: expected scalar type Long but found Float
    Linux、Unix、WindowsC语言理解查看字节序排序
    python mean()求平均值
    antd form Item设置初始值
    反向输出一个三位数
    博途PLC S7-1200/1500 ModbusTcp通信SCL代码篇(轮询)
    考公顺序步骤
    如何从800万数据中快速捞出自己想要的数据?
  • 原文地址:https://blog.csdn.net/weixin_45274937/article/details/126644755