当前位置: 首页 > article >正文

【从零开始入门unity游戏开发之——C#篇39】C#反射使用——Type 类、Assembly 类、Activator 类操作程序集

文章目录

  • 前言
  • 一、前置知识
    • 1、编译器
    • 2、程序集(`Assembly`)
    • 3、元数据(`Metadata`)
  • 二、反射
    • 1、反射的概念
    • 2、反射的作用
    • 3、反射的核心`Type` 类
      • 3.1 `Type` 类介绍
      • 3.2 不同方法获取 `Type`
      • 3.3 获取type类型所在的程序集的相关信息
    • 4、反射的常见用法
      • 4.1 获取 `Type`类型信息
      • 4.2 获取类的公共成员
      • 4.3 获取类的构造函数
      • 4.4 获取类的字段
      • 4.5 获取类的成员方法
      • 4.6 其他反射操作
    • 5、反射访问并操作私有(private)成员(包括字段、属性、方法等)
    • 6、总结
  • 三、关键类Assembly和Activator
    • 1、**反射中的关键类**
      • 1.1 `Type` 类(前面已经介绍过了)
      • 1.2 `Assembly` 类
      • 1.3 `Activator` 类
    • 2、**使用 `Activator` 类实例化对象**
      • 示例 1:无参构造函数
      • 示例 2:带参数的构造函数
    • 3、**使用 `Assembly` 类加载程序集**
      • 3.1 加载程序集:
        • (1)获取当前执行的程序集:
        • (2)从指定路径加载程序集:
      • 3.2 获取指定名称的类型
      • 3.3 获取所有类型:
      • 3.4 获取并调用类型中的方法:
    • 4、综合示例
  • 四、**总结**
  • 专栏推荐
  • 完结

前言

C# 反射 (Reflection) 是一种强大的机制,它允许程序在运行时动态地查看和操作程序的元数据。通过反射,我们可以在不事先知道类型信息的情况下,获取类型信息并操作类型中的成员(如类、方法、属性、字段等)。

一、前置知识

1、编译器

编译器是将源语言程序(如 C#、Java、C 等)翻译为目标语言程序(如机器代码或伪机器代码)的工具。源代码经过编译器的处理后,可以生成可执行文件 (.exe) 或库文件 (.dll) ,供程序运行时使用。

2、程序集(Assembly

程序集是经过编译器编译得到的中间产物,可以是一个库文件 (.dll) 或可执行文件 (.exe)。它是 .NET 程序中的代码集合,包含了类型、方法、属性等元数据,是程序执行的基础。

程序集就是我们写的一个代码集合,我们现在写的所有代码,最终都会被编译器翻译为一个程序集供别人使用。比如一个代码库文件(dll)或者一个可执行文件(exe)

我们新建一个C#项目,运行一次后,系统就会自动在bin目录里生成对应的程序集文件
在这里插入图片描述

3、元数据(Metadata

在C#中,元数据(Metadata)指的是描述程序及其组成部分(如类、方法、属性、字段等)的信息。它是关于代码的“数据”,但并不是实际执行的代码。可以将元数据视为程序代码的“数据字典”,它提供了有关类型、成员和引用等的信息,使得程序在运行时可以进行动态操作。

元数据是随代码一起编译成程序集(Assembly)的一部分,并且可以在运行时通过反射机制进行访问。

元数据是指有关程序结构和组成部分的描述信息,它不仅有助于程序的运行时类型检查,还为反射、动态加载等功能提供了支持。在C#中,元数据通常存储在程序集文件中,并可以通过反射机制访问和操作。

二、反射

1、反射的概念

反射允许程序在运行时查看其他程序集或自身的元数据,动态获取类型信息并对其进行操作。例如,通过反射可以查看类的构造函数、方法、字段、属性等,甚至可以在运行时动态创建对象并调用方法。

2、反射的作用

  • 程序运行时获取类型信息:反射可以帮助我们动态获取类、方法、字段等的元数据信息。
  • 动态操作对象:反射使得我们可以动态实例化对象,调用方法,修改字段值等。
  • 增强灵活性:反射使得代码更加灵活,尤其在插件机制、依赖注入等场景下尤为重要。

3、反射的核心Type

3.1 Type 类介绍

Type 类是反射的基础,它提供了关于类的所有信息,例如类的名称、构造函数、方法、字段、属性等。它是访问元数据的主要方式。

3.2 不同方法获取 Type

  • 通过对象的 GetType() 方法获取:

    int a = 42;
    Type type = a.GetType();
    Console.WriteLine(type);  // 输出: System.Int32
    
  • 通过 typeof 关键字获取:

    Type type = typeof(int);
    Console.WriteLine(type);  // 输出: System.Int32
    
  • 通过类的全名获取:

    Type type = Type.GetType("System.Int32");
    Console.WriteLine(type);  // 输出: System.Int32
    

    其实就是命名空间.类名
    在这里插入图片描述

3.3 获取type类型所在的程序集的相关信息

Console.WriteLine(type.Assembly);

打印

System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
  • System.Private.CoreLib 是程序集的名称。(通常是 .exe 或 .dll 文件的名称)
  • Version=9.0.0.0 是程序集的版本。
  • Culture=neutral 表示它是通用区域设置的。(如果是通用语言程序集,则为 neutral)。
  • PublicKeyToken=7cec85d7bea7798e 是公共密钥标识符。(用于区分同名的不同程序集版本)。

4、反射的常见用法

为了方便测试反射的常见用法,这里我先新增一个简单的类,里面包含基本的构造函数、方法、字段、属性。

ps:这里只是测试探究,实际开发肯定不需要通过反射来操作自己的类。

class Test
{
    private int i = 1;
    public int j = 2;
    public int k { get; set; }
    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 Log()
    {
        Console.WriteLine("学习C#");
    }
}

4.1 获取 Type类型信息

Type t = typeof(Test);// 获取类型信息

4.2 获取类的公共成员

使用 GetMembers() 方法获取类中的所有公共成员:

MemberInfo[] infos = t.GetMembers();
foreach (var info in infos)
{
    Console.WriteLine(info);
}

打印结果
在这里插入图片描述

可以看到,除了万物之父(object)中的方法,打印出来的都是我们前面Test类中定义的公共成员信息(构造函数、方法、字段、属性)。

4.3 获取类的构造函数

  • 获取所有构造函数:
ConstructorInfo[] ctors = t.GetConstructors();
foreach (var ctor in ctors)
{
    Console.WriteLine(ctor);
}

结果
在这里插入图片描述

  • 获取无参构造函数并调用:
ConstructorInfo ctor = t.GetConstructor(Type.EmptyTypes);
Test obj = (Test)ctor.Invoke(null);
Console.WriteLine(obj);
  • 获取有参构造函数并调用:
ConstructorInfo ctor = t.GetConstructor(new Type[] { typeof(int), typeof(string) });
Test obj = (Test)ctor.Invoke(new object[] { 10, "Hello" });
Console.WriteLine(obj);

4.4 获取类的字段

  • 获取所有公共字段:
FieldInfo[] fieldInfos = t.GetFields();
foreach (var field in fieldInfos)
{
    Console.WriteLine(field);
}

结果
在这里插入图片描述

  • 获取指定字段:
FieldInfo fieldInfo = t.GetField("j");
Console.WriteLine(fieldInfo);

结果
在这里插入图片描述

  • 使用反射获取和设置字段的值:
FieldInfo fieldInfo = t.GetField("j");
Test test = new Test();
test.j = 99;
fieldInfo.SetValue(test, 100);
Console.WriteLine(fieldInfo.GetValue(test));  // 输出: 100

4.5 获取类的成员方法

  • 获取所有公共方法(记得引入using System.Reflection;反射命名空间):
using System.Reflection;

MethodInfo[] methods = t.GetMethods();
foreach (var method in methods)
{
    Console.WriteLine(method);
}
  • 获取指定方法并调用:
MethodInfo method = t.GetMethod("ToString");
object result = method.Invoke(test, null);
Console.WriteLine(result);
  • 对于方法重载,需要提供方法的参数类型:
MethodInfo method = t.GetMethod("Substring", new Type[] { typeof(int), typeof(int) });
object result = method.Invoke("Hello, World!", new object[] { 7, 5 });
Console.WriteLine(result);  // 输出: World

4.6 其他反射操作

  • 枚举
    获取枚举类型的名称:

    Enum.GetEnumName(typeof(DayOfWeek), 1);  // 输出: Monday
    
  • 事件
    获取类的事件:

    EventInfo eventInfo = t.GetEvent("MyEvent");
    Console.WriteLine(eventInfo);
    
  • 接口
    获取类实现的接口:

    Type[] interfaces = t.GetInterfaces();
    foreach (var iface in interfaces)
    {
        Console.WriteLine(iface);
    }
    
  • 属性
    获取类的属性:

    PropertyInfo[] properties = t.GetProperties();
    foreach (var property in properties)
    {
        Console.WriteLine(property);
    }
    

5、反射访问并操作私有(private)成员(包括字段、属性、方法等)

在默认情况下,反射是可以访问私有成员的,但你必须明确告诉 .NET 你要访问它们。具体来说,你需要使用 BindingFlags 来指定访问私有成员。

例如,默认情况下,反射会忽略私有字段和方法,但你可以通过显式地指定 BindingFlags.NonPublicBindingFlags.Instance 来让反射能够访问这些私有成员。

示例

// 创建 MyClass 的实例
MyClass myObject = new MyClass();

// 获取 MyClass 类型的信息
Type type = typeof(MyClass);

// 使用反射访问私有字段 secretValue
FieldInfo fieldInfo = type.GetField("secretValue", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
    // 获取字段的值
    var value = fieldInfo.GetValue(myObject);
    Console.WriteLine("Private Field 'secretValue' Value: " + value);
}

// 使用反射访问私有方法 PrintSecret
MethodInfo methodInfo = type.GetMethod("PrintSecret", BindingFlags.NonPublic | BindingFlags.Instance);
if (methodInfo != null)
{
    // 调用私有方法
    methodInfo.Invoke(myObject, null);
}

这种机制虽然强大,但应谨慎使用,避免破坏封装性,尤其是在生产环境中。这种做法通常用于调试、测试、动态代理等特殊场景。

6、总结

C# 中的反射为程序提供了强大的动态能力,通过 Type 类及其提供的方法,我们可以在运行时获取类型的元数据,动态创建对象,调用方法,甚至修改字段值。

虽然反射提供了灵活性,但是反射的性能较差,因为它是动态查找和操作的。如果频繁使用反射,可能会影响程序性能,尤其是在处理大量数据时。因此应该在合适的场合使用反射,并考虑到性能和可维护性。

三、关键类Assembly和Activator

反射(Reflection)是 C# 中一个非常强大的机制,它允许在运行时访问和操作程序中的类型信息。通过反射,程序可以在运行时动态地获取类、方法、属性等信息,并实例化对象、调用方法、访问字段等。下面是对 AssemblyActivator 这两个关键类的详细介绍和相关用法。

1、反射中的关键类

1.1 Type 类(前面已经介绍过了)

Type 类提供了对类型信息的访问,它是所有类型的元数据的基础。通过 Type 对象,你可以获取类的构造函数、属性、字段、方法等信息。

1.2 Assembly

Assembly 类用于加载和操作程序集。程序集包含了类、接口、枚举等信息,反射可以用来加载程序集,并且在程序运行时查找类型和操作它们。

1.3 Activator

Activator 类位于 System 命名空间下,提供了一组静态方法来创建类型的实例。它常用于在运行时通过反射动态创建对象,特别是在我们事先不知道具体类型的情况下。例如,依赖注入、插件加载、对象池等场景中,可以使用 Activator 来动态实例化对象。

2、使用 Activator 类实例化对象

Activator 提供了几种创建对象的方法,最常用的是 CreateInstance,它可以用来根据 Type 对象动态创建实例。

示例 1:无参构造函数

假设有一个类 Test,它有一个无参构造函数。你可以使用 Activator.CreateInstance 来创建该类的实例。

using System;

public class Test
{
    public string str = "Hello, World!";
}

class Program
{
    static void Main()
    {
        // 获取 Test 类的 Type 对象
        Type testType = typeof(Test);
        
        // 使用 Activator 创建对象实例
        Test testobj = Activator.CreateInstance(testType) as Test;
        
        // 输出结果
        Console.WriteLine(testobj.str);  // Output: Hello, World!
    }
}

示例 2:带参数的构造函数

如果类有带参数的构造函数,可以使用 Activator.CreateInstance 方法传递参数来创建实例。

using System;

public class Test
{
    public int j;
    public string str;

    // 构造函数
    public Test(int j, string str)
    {
        this.j = j;
        this.str = str;
    }
}

class Program
{
    static void Main()
    {
        Type testType = typeof(Test);
        
        // 使用带参数的构造函数
        Test testobj1 = Activator.CreateInstance(testType, 99, "Hello") as Test;
        Console.WriteLine(testobj1.j);  // Output: 99
        Console.WriteLine(testobj1.str);  // Output: Hello
        
        Test testobj2 = Activator.CreateInstance(testType, 55, "World") as Test;
        Console.WriteLine(testobj2.j);  // Output: 55
        Console.WriteLine(testobj2.str);  // Output: World
    }
}

3、使用 Assembly 类加载程序集

前面我们一直都是对当前程序的操作,实际开发肯定是操作外部程序集。Assembly 类使得我们可以在运行时加载、查询、获取外部程序集的类型信息等。

常用方法:

  • GetExecutingAssembly():获取当前执行的程序集。
  • LoadFrom(string path):从指定路径加载程序集。
  • GetTypes():获取程序集中的所有类型。
  • GetType(string typeName):获取指定名称的类型。
  • GetManifestResourceNames():获取程序集中的所有嵌入式资源名称。

3.1 加载程序集:

(1)获取当前执行的程序集:
Assembly assembly = Assembly.GetExecutingAssembly();
Console.WriteLine("当前程序集: " + assembly.FullName);
(2)从指定路径加载程序集:
Assembly assembly = Assembly.LoadFrom("path/to/your/assembly.dll");
Console.WriteLine("加载程序集: " + assembly.FullName);

3.2 获取指定名称的类型

Assembly assembly = Assembly.LoadFrom("path/to/your/assembly.dll");
Type type = assembly.GetType("Namespace.ClassName"); // 获取程序集中的某个类型
Console.WriteLine("类型名称: " + type.FullName);

3.3 获取所有类型:

Assembly assembly = Assembly.LoadFrom("path/to/your/assembly.dll");
Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
    Console.WriteLine(t.FullName); // 输出程序集中的所有类型
}

3.4 获取并调用类型中的方法:

其实就是结合前面Type知识配合使用

Assembly assembly = Assembly.LoadFrom("path/to/your/assembly.dll");
Type type = assembly.GetType("Namespace.ClassName");
MethodInfo methodInfo = type.GetMethod("MethodName");
object result = methodInfo.Invoke(null, null); // 调用静态方法
Console.WriteLine(result);

4、综合示例

我这里项目的程序集文件路径为:C:\Users\zw\Desktop\C#\bin\Debug\net9.0\C#.dll
在这里插入图片描述
程序集文件里主要的代码是,我自定义了一个Test类
在这里插入图片描述

代码

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        // 加载程序集(因为是反斜杠,所以加个@不希望它转义)
        Assembly assembly = Assembly.LoadFrom(@"C:\Users\zw\Desktop\C#\bin\Debug\net9.0\C#.dll");
        
        // 获取程序集中的所有类型
        Type[] types = assembly.GetTypes();
        
        foreach (var type in types)
        {
            Console.WriteLine(type.Name);
        }
        
        // 获取程序集中的一个具体类型
        Type testType = assembly.GetType("Test");
        
        // 使用反射实例化对象
        object myClassObj = Activator.CreateInstance(testType);

        // 获取并调用方法
        MethodInfo method = testType.GetMethod("Log");
        method.Invoke(myClassObj, null);

        // 获取指定字段并打印值
        FieldInfo field1 = testType.GetField("str");
        Console.WriteLine(field1.GetValue(myClassObj));

        // 获取指定字段,先修改值,在打印值
        FieldInfo field2 = testType.GetField("j");
        field2.SetValue(myClassObj, 100);
        Console.WriteLine(field2.GetValue(myClassObj));
    }
}

结果,程序集里的方法被执行,字段也被打印了(Program类和E_Days枚举是定义的其他两个测试数据,不用管即可)
在这里插入图片描述

四、总结

  • Type:提供关于类、接口、枚举等类型的信息。
  • Assembly:用于加载程序集,可以获取程序集中的所有类型和类型成员信息。
  • Activator:用于在运行时动态创建对象实例,支持无参数和带参数构造函数。
  • 反射虽然非常强大,但会影响性能,因此在需要时使用,避免过度依赖。

专栏推荐

地址
【从零开始入门unity游戏开发之——C#篇】
【从零开始入门unity游戏开发之——unity篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架开发】

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述


http://www.kler.cn/a/463625.html

相关文章:

  • 设计模式之桥接设计模式
  • 每天40分玩转Django:Django Celery
  • Web安全 - “Referrer Policy“ Security 头值不安全
  • 单元测试4.0+思路总结
  • MinGW 和 MinGW-w64 的介绍与配置
  • 【Vue3】h、ref:vue3的两个新特性(重要)-h和ref
  • 如何利用java爬虫获得AMAZON商品详情
  • 基于 Python 的人脸识别景区票务识别系统
  • 使用Qt中的模型视图框架
  • 【Rust自学】9.1. 不可恢复的错误以及panic!
  • 180天Java项目学习路线指引
  • 计算机毕设-基于springboot的花店管理系统的设计与实现(附源码+lw+ppt+开题报告)
  • 低精度只适用于未充分训练的LLM?腾讯提出LLM量化的scaling laws
  • JVMTI 笔记
  • 单元测试入门和mockup
  • ruoyi 分页 查询超出后还有数据; Mybatis-Plus 分页 超出后还有数据
  • 常见CMS漏洞(wordpress,DedeCms,ASPCMS,PHPMyAdmin)
  • MATLAB 中打印某些变量的值到文本文件中,使用diary和 fprintf
  • 人工智能:变革时代的核心驱动力
  • 阿里云redis内存优化——PCP数据清理
  • 华为开源自研AI框架昇思MindSpore应用案例:ICNet用于实时的语义分割
  • C# 将图片转换为PDF文档
  • 虹安信息技术有限公司数据泄露防护平台pushSetup存在SQL注入漏洞
  • 【Elasticsearch入门到落地】5、安装IK分词器
  • [最佳方法] 如何将视频从 Android 发送到 iPhone
  • Windows操作系统部署Tomcat详细讲解