C#进阶:预处理指令/反射,Gettype,Typeof/关键类

什么是编译器

  • 定义 :编译器是一种翻译程序,用于将源语言程序 翻译为目标语言程序
    • 源语言程序:用C#、C、C++、Java等程序设计语言编写的程序
    • 目标语言程序:二进制数表示的伪机器代码程序
  • 作用原理:将人类可读的代码单词翻译成计算机能识别的二进制指令
  • 编译时机:在Visual Studio运行代码时会自动进行编译过程

预处理器

什么是预处理器

  • 定义:指导编译器在实际编译开始前对信息进行预处理的指令
  • 语法特征:
    • 以#号开头
    • 不是语句,不以分号结束
  • 常见用途:代码折叠功能就是预处理器指令的应用
  • 执行时机:在源代码编译之前执行

常见的预处理器符号

#define

注意事项:

在实际的Unity或C#跨平台开发中,你通常不需要在脚本里手动写 #define(除非是写非常特殊的编辑器工具)。

这是因为:当你把代码放入Unity项目中时,Unity编辑器会自动为你定义好这些符号 (例如UNITY_IOSUNITY_ANDROID等),无需手动定义。

  • 功能:定义一个符号,类似没有值的变量
  • 语法:#define 符号名
    • 取消定义:使用#undef取消已定义的符号
  • 位置要求:必须写在脚本文件最前面(using语句之前)
    • 配合使用:通常与if指令或特性配合使用
    • 实际应用:常用于定义Unity版本或平台标识符
cs 复制代码
//定义一个符号
#define Unity4
#define IOS
#define Android
//取消定义一个符号
#undef Android

#region 命名空间
using System;
using System.Threading;
#endregion

if else

  • 组合判断:支持逻辑与(&&)和逻辑或(||)进行多符号组合
  • 编译控制:决定哪些代码块会被编译器翻译
  • 实际应用:根据不同Unity版本或平台编译不同代码
cs 复制代码
//定义一个符号
#define Unity4
#define IOS
#define Android

//和if语句规则一样,一般配合#define定义的符号使用
//用于告诉编译器进行编译代码的流程控制
//如果发现有Unity4这个符号那么其中包含的代码就会被编译器翻译
namespace Lesson_预处理器指令
{
    class Program
    {
        static void Main(string[] args)
        {
    //如果定义了Unity4和IOS则执行以下代码
    #if Unity4 && IOS
            Console.WriteLine("版本为Unity4 IOS");
            //警告
            #warning 这个版本不合法
            //报错
            #error 这个版本不准执行
    #endif
        }
    }
}

warning/error

cs 复制代码
//定义一个符号
#define Unity4
#define IOS
#define Android

//和if语句规则一样,一般配合#define定义的符号使用
//用于告诉编译器进行编译代码的流程控制
//如果发现有Unity4这个符号那么其中包含的代码就会被编译器翻译
namespace Lesson_预处理器指令
{
    class Program
    {
        static void Main(string[] args)
        {
    //如果定义了Unity4和IOS则执行以下代码
    #if Unity4 && IOS
            Console.WriteLine("版本为Unity4 IOS");
            //警告
            #warning 这个版本不合法
            //报错
            #error 这个版本不准执行
    #endif
        }
    }
}
  • #warning:让编译器产生警告信息
  • #error:让编译器产生错误信息,阻止编译通过
    • 典型应用:配合条件编译,在特定条件下提示警告或报错

region折叠代码

cs 复制代码
//折叠代码
#region 命名空间
using System;
using System.Threading;
#endregion

概念和关键类

程序集

  • 本质: 程序集是经由编译器编译得到的中间产物,供进一步编译执行使用
    • 程序集是包含中间语言代码(IL)​元数据(Metadata)​清单(Manifest)​资源文件 的逻辑单元,其物理表现形式为.exe.dll文件
      • 程序集表现形式: 在Windows系统中通常表现为两种文件格式
        • 库文件: 后缀为.dll
          • .dll 文件是 动态链接库 (Dynamic Link Library)。它不能直接运行,必须由 .exe 或者其他 DLL 调用。
        • 可执行文件: 后缀为.exe
          • .exe 文件是是程序的起点,是你双击后能够直接运行的文件。
  • 通俗理解: 程序集就是我们编写的代码集合经过编译器翻译后的产物
    • 例如:当前编写的控制台程序最终会被编译为Lesson20.dll或Lesson20.exe
    • 案例如下图所示
  • 生成过程 :通过Visual Studio编译器自动完成翻译工作
    • 编译前:bin目录为空
    • 编译后:生成.dll和.exe文件

元数据

  • 元数据本质:元数据是"描述数据的数据",在程序中表现为类、函数、变量等类型信息。
  • 存储关系:所有元数据都保存在程序集中,程序集就是我们编写的代码集合经过编译器翻译后的产物。
cs 复制代码
你写的代码(源代码):
public class Calculator 
{
    public int Add(int a, int b) 
    {
        return a + b;
    }
}

当你编译这段代码后,元数据会以二进制表格形式存储在程序集(.dll或.exe文件)中,具体包括:
类型定义表:记录 Calculator 类的基本信息
方法定义表:记录 Add 方法的签名、返回类型和参数
字段定义表:记录类中的成员变量(本例中没有)
属性定义表:记录类的属性(本例中没有)

反射的概念

  • 核心定义:程序运行时查看自身或其他程序集元数据的行为称为反射。
    • 就是在程序运行时,通过反射我们可以得到其他程序集,或者我自己这个程序集代码的各种信息。
    • 这各种信息其实就是这个元数据信息,就是我可以通过代码的形式得到我自己写的代码的类,函数,成员变量等等------我们可以通过这种反射的形式去实例化它们,执行它们。

反射最大的作用:

我不用写一些实际上的逻辑代码。是通过反射去调用别人程序集里面写的代码来做一些功能这个就是反射的一个最大的作用。

具体操作:

利用反射去访问别人的这个程序集:

比如说我可以访问这个listen 18练习题的这个dll文件,listen 18练习题 程序集中其实是写了一些代码的,但是我当前程序中即使没有写相关的代码。

但是我可以通过得到它的程序集,然后得到它里面元数据的信息,就是它类的信息,然后在我的这个程序里面去new listen 18练习题 中的方块类进来。

关键类------Type类

此为 信息类,它是反射功能的基础,它是访问元数据的主要方式

使用Type类 用于获取有关类型声明的信息,比如说构造函数方法字段。属性和类的事件等等信息。

有关类型的成员:比如说构造函数方法字段,属性和类的事件等等信息

万物之父获取形式Type

GetType()函数是万物之父object类型自带的

cs 复制代码
using System;
using System.Reflection;

namespace Lesson_13_反射和特性
{
    
    class Program
    {
        static void Main(string[] args)
        {
            //1.万物之父object中GetType()可以获取对象的Type
            int a = 42;
            Type type = a.GetType();
            Console.WriteLine(type);
        }
    }
}

输出结果:System.Int32

其实它就把这个int32这个类里面所有的信息。将相关成员方法,成员变量,属性,事件等等的信息都存储在了这个Type里面。

typeof关键字获取Type

cs 复制代码
class Program
{
    static void Main(string[] args)
    {
        //2.通过typeof关键字 传入类名也可以得到对象的Type
        Type type2 = typeof(int);
        Console.WriteLine(type2);
    }
}

输出结果:System.Int32

.通过命名空间+类名 获取Type

cs 复制代码
using System;
using System.Reflection;

namespace Lesson_13_反射和特性
{
   
    class Program
    {
        static void Main(string[] args)
        {
            //3.通过类名 获取类型 要包含命名空间
            Type type3 = Type.GetType("Int32");//1、这里输出无东西
            Type type3 = Type.GetType("System.Int32");//2、注意int32的命名空间是System
                                                      //这里可以输出int32

            Console.WriteLine(type3);
        }
    }
}

关键点:三个Type实际指向数据是一样的

对Type类后执行操作

这里一般不会获取自己的程序集------主要是为学习获取别人的程序集做铺垫用

cs 复制代码
//1、获取Type类
在同一个程序集内------关键字typeof
 Type t = typeof(Test);
不同程序集内------命名空间+类名
 Type type3 = Type.GetType("System.Int32");

//2、得到类的程序集
Console.WriteLine(type.Assembly);

获取类中全部成员

MemberInfo[] infos = t.GetMembers();

注意:

MemberInfo[],后面的Fieldinfo,Methodinfo这些类型都是用来接收加载其他程序集中的类的参数------因为有些参数成员当前程序中

cs 复制代码
class Test
    {
        private int i = 1;
        public int j = 10;
        public string str = "你是";

        public Test ()
        {

        }
        public Test (int i)
        {
            this.i = i;
        }
        public Test (int i,string str): this(i)
        {
            this.str = str;
        }
        public void Spek()
        {
            Console.WriteLine(i);
        }
    }

 class Program
 {
     static void Main(string[] args)
     {    
         //获取类中的所有公共成员
         Type t = typeof(Test);
         MemberInfo[] infos = t.GetMembers();
         for (int i = 0; i < infos.Length; i++)
         {
             Console.WriteLine(infos[i]);
         }
     }
 }

输出公共成员情况如下:

为什么int32 的i变量没有被输出

只看到一个 Int32 类型的输出(对应 j),核心原因是反射的 GetMembers() 方法默认只获取「公共(public)」成员 ,而 private int i 是私有成员,所以被过滤掉了。
输出成员顺序为什么与类中成员顺序不一致?

反射的 GetMembers() 方法返回的成员顺序,并不是你在代码中编写的顺序:

CLR 的内部排序规则(核心原因).NET 的公共语言运行时(CLR)在返回反射成员列表时,会遵循一套内部排序逻辑,而不是按代码书写顺序:

  • 成员类型优先级 :方法(Method)会优先于字段(Field)输出。Spek()方法(MethodInfo 类型) ,而 i/j/str字段(FieldInfo 类型),所以方法会排在所有字段前面。
  • 同类型成员排序:如果是同一类成员(比如都是方法 / 都是字段),会按成员名称的哈希值、字母顺序(或元数据中的存储顺序)排序,而非代码编写顺序。

获取类中构造函数

获取类中全部构造函数

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    class Test
    {
       ..................
    }

    class Program
    {
        static void Main(string[] args)
        {
            //获取类中的所有公共成员
            Type t = typeof(Test);
         
            //1.获取所有构造函数
            ConstructorInfo[] ctors = t.GetConstructors();
            for (int i = 0; i < ctors.Length; i++)
            {
                Console.WriteLine(ctors[i]);
            }
            
        }
    }
}

获取类中无参构造函数

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    class Test
    {
       ..................
    }

    class Program
    {
        static void Main(string[] args)
        {
            //获取类中的所有公共成员
            Type t = typeof(Test);

            //2.获取其中一个构造函数 并执行
            //得构造函数传入 Type数组 数组中内容按顺序是参数类型
            //执行构造函数传入 object数组 表示按顺序传入的参数
            //2-1得到无参构造
            ConstructorInfo info = t.GetConstructor(new Type[0]);
            //执行无参构造 无参传null
            Test obj = info.Invoke(null) as Test;
            Console.WriteLine(obj.j);

        }
    }
}

ConstructorInfo info = t.GetConstructor(new Type[0]);

new Type[0] 的含义是:创建一个长度为 0 的 Type 类型数组

简单来说,它代表**"没有参数"------**这里就是相当于无参构造函数的条件

info.Invoke(null)

执行无参构造函数,括号中无参数,传null即可------这里返回一个万物之父object,然后可以使用子类Test进行替换

含参构造函数

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    class Test
    {   
        public int j = 10;
       ........................
        public Test(int j)
        {
            this.j = j;
        }
       ..................
    }

    class Program
    {
        static void Main(string[] args)
        {
            //获取类中的所有公共成员
            Type t = typeof(Test);

            //2-2得到有参构造 (int)
            //这里选择哪种构造函数(int 类型的 变量为j)
            ConstructorInfo info2 =t.GetConstructor(new Type[] { typeof(int) });
            //这里传入参数(2)进行构造
            Test obj = info2.Invoke(new object[] { 2 }) as Test;
            Console.WriteLine(obj.j);

             //得到有参构造 (int,string)
            ConstructorInfo info3 = 
                t.GetConstructor(new Type[] { typeof(int), typeof(string) });
            obj = info3.Invoke(new object[] { 10, "你好世界" }) as Test;
            Console.WriteLine(obj.str)

        }
    }
}

获取类的公共成员 变量

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    class Test
    {
        private int i = 1;
        public int j = 10;
        public string str = "你是";

        public Test()
        {

        }
        public Test(int j)
        {
            this.j = j;
        }
        public Test(int i, string str) : this(i)
        {
            this.str = str;
        }
        public void Spek()
        {
            Console.WriteLine(i);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Type t = typeof(Test);
            //1.得到所有成员
            FieldInfo[] fieldInfos = t.GetFields();
            for (int i = 0; i < fieldInfos.Length; i++)
            {
                Console.WriteLine(fieldInfos[i]);
            }
            //2.得到指定名称的公共变量成员
            FieldInfo info4 = t.GetField("j");
            Console.WriteLine(info4);

        }
    }
}

那么如何获取某个具体对象的值呢?

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    class Test
    {
        ............
    }

    class Program
    {
        static void Main(string[] args)
        {
            //先获取对应类的对应变量
            Type t = typeof(Test);
            FieldInfo info4 = t.GetField("j");
            Console.WriteLine(info4);
            // 3-1通过反射 获取对象的某个变量的值
            Test test = new Test();
            test.j = 99;
            test.str = "苏苏苏";
            Console.WriteLine(info4.GetValue(test));
            // 3-2通过反射 设置指定对象的某个变量的值
            info4.SetValue(test, 999);
            Console.WriteLine(info4.GetValue(test));
        }
    }
}

获取类的公共方法

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    class Test
    {
        ..................
    }

    class Program
    {
        static void Main(string[] args)
        {
            //获取类的公共成员 方法            
            Type strType = typeof(string);
            //1.得到所有成员            
            MethodInfo[] methods = strType.GetMethods();
            for (int i = 0; i < methods.Length; i++)
            {
                Console.WriteLine(methods[i]);
            }
            //2.得到指定方法            
            MethodInfo info5 = strType.GetMethod("Substring",
            new Type[] { typeof(int), typeof(int) });
            //3.调用方法            
            //注意:如果是静态方法 Incoke中的第一个参数传null即可            
            string str = "Hello,World!";
            object result = info5.Invoke(str, new object[] { 6, 5 });
            Console.WriteLine(result);
        }
    }
}

关键类------Activator类

功能:用于快速实例化对象的类,通过Type对象快捷创建实例

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    class Test
    {
        private int i = 1;
        public int j = 10;
        public string str = "你是";

        public Test()
        {

        }
        public Test(int j)
        {
            this.j = j;
        }
        public Test(int i, string str) : this(i)
        {
            this.str = str;
        }
        public void Spek()
        {
            Console.WriteLine(i);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //Activator
            //用于将Type对象快捷实例化为对象
            Type testType = typeof(Test);
            //1.无参构造------无参需要传入一个Type类型 变量名字testType快速进行实例化
            Test testObj = Activator.CreateInstance(testType) as Test;
            Console.WriteLine(testObj.str);
            //2.有参构造------这里传入参数顺序是根据构造函数传参顺序来的
            testObj = Activator.CreateInstance(testType, 99) as Test;
            testObj = Activator.CreateInstance(testType, 888, "你好你好") as Test;
            Console.WriteLine(testObj.str);
        }
    }
}

关键类------Assembly类

  • 功能:程序集类,用于加载其他程序集(如dll文件)
  • 作用:加载后才能使用Type获取该程序集中的类型信息
  • 加载方法
    • Load方法:加载同一文件夹下的程序集
    • LoadFrom方法:加载不同路径的程序集(需完整路径)
    • LoadFile方法:通过完全限定路径加载

检查指定程序集中的类

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    class Program
    {
        static void Main(string[] args)
        {
            //1.先加载一个指定的程序集
            Assembly assembly = Assembly.LoadFrom(@"C:\Users\xiao\Desktop\duck\duck\bin\Debug\net8.0\duck");
            Type[] types = assembly.GetTypes();
            
            for (int i = 0; i < types.Length; i++)
            {
                Console.WriteLine(types[i]);
            }
        }
    }
}

注意,如果指定程序集要顺利加载,必须F5编译通过才行

cs 复制代码
using System;
// 随机生成的示例类
internal class Txt
{
    public int Id { get; set; }
    public string Name { get; set; }

    public Txt(int id, string name)
    {
        Id = id;
        Name = name;
    }

}

internal static class Program
{
    private static void Main(string[] args)
    {          
    }
}

输出结果如图所示:

找到对应程序集中的类后开始实例化

cs 复制代码
using System;
using System.Reflection;
using static System.Net.Mime.MediaTypeNames;

namespace Lesson_13_反射和特性
{
    
    class Program
    {
        static void Main(string[] args)
        {
            // 1. 加载程序集(确保路径正确)
            string assemblyPath = @"C:\Users\xiao\Desktop\duck\duck\bin\Debug\net8.0\duck";
            Assembly assembly = Assembly.LoadFrom(assemblyPath);

            // 2. 获取Duck类型(使用全限定名)
            Type duckType = assembly.GetType("Duck");

            // 3. 创建Duck实例(确保参数类型匹配)
            object duckObj = Activator.CreateInstance(duckType, "黄色", 5);

            Console.WriteLine(duckObj);
        }
    }
}
相关推荐
无小道3 小时前
Qt-qrc机制简单介绍
开发语言·qt
zhooyu3 小时前
C++和OpenGL手搓3D游戏编程(20160207进展和效果)
开发语言·c++·游戏·3d·opengl
HAPPY酷3 小时前
C++ 和 Python 的“容器”对决:从万金油到核武器
开发语言·c++·python
大鹏说大话3 小时前
告别 MSBuild 脚本混乱:用 C# 和 Nuke 构建清晰、可维护的现代化构建系统
开发语言·c#
Mr_sun.4 小时前
Day09——入退管理-入住-2
android·java·开发语言
MAGICIAN...4 小时前
【java-软件设计原则】
java·开发语言
gpfyyds6664 小时前
Python代码练习
开发语言·python
盐真卿4 小时前
python第八部分:高级特性(二)
java·开发语言
茉莉玫瑰花茶4 小时前
C++ 17 详细特性解析(5)
开发语言·c++·算法