C# 进阶 -反射(Reflection)

C# 反射概述

反射(Reflection) 是 .NET 框架提供的一个强大功能,允许程序在运行时检查和操作类型的信息。通过反射,可以动态地创建对象、调用方法、访问属性和字段等。

反射提供了封装程序集、模块和类型的对象(Type类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了特性,可以利用反射对它们进行访问。

 

exe和dll的区别

在 Windows 操作系统中,EXE 和 DLL 是两种常见的可执行文件格式,它们各自有不同的用途和特点。

1. 定义

  • EXE (Executable): 可执行文件,用于启动独立的应用程序。当用户双击一个 EXE 文件时,操作系统会加载并运行该应用程序。
  • DLL (Dynamic Link Library): 动态链接库,包含可以被多个程序共享的代码和数据。DLL 文件不能直接运行,而是被其他程序在运行时加载和使用。

2. 用途

  • EXE:
    • 用于创建独立的应用程序。
    • 包含应用程序的入口点(即 Main 方法)。
    • 通常包含用户界面和业务逻辑。
  • DLL:
    • 用于封装可重用的代码和资源。
    • 可以被多个应用程序共享,减少内存占用和磁盘空间。
    • 常用于实现模块化设计,便于维护和更新。

3. 结构

  • EXE:
    • 包含一个或多个入口点(如 Main 方法)
    • 可以包含资源文件(如图标、位图、字符串表等)。
  • DLL:
    • 不包含入口点
    • 可以包含类、方法、属性、字段等。
    • 可以导出函数和类,供其他程序调用。

4. 加载方式

  • EXE:
    • 由操作系统直接加载并运行。
    • 通常在进程的地址空间中独立运行。
  • DLL:
    • 由其他程序在运行时通过 LoadLibrary  DllImport 等机制加载。
    • 可以在多个进程中共享同一个 DLL 实例。
  1. 性能和资源管理
  • EXE:
    • 每个 EXE 文件都是一个独立的进程,占用独立的内存空间。
    • 启动时间可能较长,因为需要加载整个应用程序。
  • DLL:
    • 可以在多个进程中共享,节省内存。
    • 加载速度快,因为只需要加载一次即可被多个程序使用。

 

C# metadata元数据

在 C# 和 .NET 框架中,元数据是指存储在程序集(如 EXE 或 DLL 文件)中的附加信息。这些信息描述了程序集中的类型、成员、属性、方法等。元数据对于反射、序列化、属性(Attributes)等功能至关重要。

1. 元数据的内容

元数据通常包括以下内容:

  • 类型信息:类、结构、枚举、接口等。
  • 成员信息:字段、属性、方法、事件等。
  • 参数信息:方法和属性的参数。
  • 自定义属性:用户定义的属性,用于提供额外的元数据信息。
  • 版本信息:程序集的版本号。
  • 依赖信息:程序集依赖的其他程序集。

2. 元数据的作用

  • 反射:通过反射可以动态地获取类型和成员的信息,并在运行时进行操作。
  • 序列化:元数据帮助序列化器了解如何将对象转换为字节流。
  • 调试:调试器使用元数据来显示变量和类型的详细信息。
  • 编译器:编译器使用元数据来验证类型和成员的正确性。
  • 工具:各种开发工具(如 Visual Studio)使用元数据来提供智能感知、代码生成等功能。

 

 

如何生成dll

解决方案

选择类库

然后在这里面写一堆类

右键类库,点生成或重新生成

文件资源管理器打开

找到bin文件夹,往下找,这个就是生成的dll

 

 

C# 反射用途

主要用途:

1. 动态加载程序集:可以通过 Assembly类来动态加载程序集,然后从中获取类型信息。

2. 创建和操作对象:使用 Activator.CreateInstance方法可以根据类型信息在运行时创建对象实例。

3. 访问私有成员:虽然通常不推荐这样做,但反射允许访问类中的私有字段和方法。

4. 获取类型信息:可以获取类的公共属性、字段、方法等的所有信息。

5. 调用方法:即使在编译时不知道方法的名字,也可以通过反射在运行时调用它们。

6. 设置和获取属性值:可以动态地读取或设置对象的属性值。

 

基本示例

反射加载程序集

使用 Assembly.Load 方法

用途:从全局程序集缓存(GAC)或应用程序的基础目录及其子目录中加载程序集。

优点:简单易用,适用于已知路径的程序集。

缺点:不能加载不在基础目录或GAC中的程序集。

Assembly ToolAssembly = Assembly.Load( "ClassLibrary" );

 

 使用 Assembly.LoadFrom 方法

用途:从指定路径加载程序集,路径可以是绝对路径或相对路径。

优点:可以加载不在基础目录或GAC中的程序集。

缺点:可能会导致程序集加载冲突,因为多个程序集可能具有相同的名字但不同的路径。

Assembly ToolAssembly = Assembly.LoadFrom( "../../../dll/ClassLibrary.dll" );
Assembly ToolAssembly = Assembly.LoadFrom( @"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll" );

这个相对路径是以生成的exe所在目录来相对的

 

 

 

 使用 Assembly.LoadFile 方法

用途:从指定文件路径加载程序集,不考虑程序集的依赖关系。

优点:可以加载任何文件路径的程序集。

缺点:不会解析程序集的依赖关系,可能导致运行时错误。

Assembly ToolAssembly = Assembly.LoadFile(@"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll");

 

使用 AppDomain.CurrentDomain.Load 方法

用途:在指定的应用域中加载程序集。

优点:可以在不同的应用域中加载相同的程序集,适用于需要隔离的场景。

缺点:需要显式管理应用域的生命周期。

byte[] assemblyBytes = File.ReadAllBytes(@"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll");
Assembly ToolAssembly = AppDomain.CurrentDomain.Load(assemblyBytes);

 

反射读取程序集内类型名称

//加载程序集
byte[] assemblyBytes = File.ReadAllBytes(@"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll");
Assembly ToolAssembly = AppDomain.CurrentDomain.Load(assemblyBytes);

//输出类型名称
foreach (var type in ToolAssembly.GetTypes())
{
    Console.WriteLine(type.Name);
}
//Tool
//Student
//MyGenericClass`1

反射创建对象

无参数的构造函数

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Student()
    {
        Name = "";
        Age = 0;
    }

    public  void ShowInfo()
    {
        Console.WriteLine($"My Name is {Name},I'm   {Age} years old. ");
    }
}

我们定义了一个Student类,这个类是有一个无参数构造函数的。

//获取Student类的Type对象
Type StudentType = typeof(Student);

//创建实例
object StudentInstance = Activator.CreateInstance(StudentType);

有参数的构造函数

在 C# 中,如果你没有显式地定义任何构造函数,编译器会自动为你添加一个无参数的构造函数。但是一旦你定义了有参数的构造函数,编译器就不会再为你添加无参数的构造函数。可以看到下面这个Student类是没有构造函数的。

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }

    public  void ShowInfo()
    {
        Console.WriteLine($"My Name is {Name},I'm   {Age} years old. ");
    }
}

执行创建对象是不会报错的。但是看下面这个定义了有参数构造函数的Student类

    public class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Student(string name, int age)
        {
            Name = name;
            Age = age;
        }

        public  void ShowInfo()
        {
            Console.WriteLine($"My Name is {Name},I'm   {Age} years old. ");
        }
    }

这个再使用上面的创建对象方式就会报错。会提示没有找到无参数的构造函数。

这种就需要使用Activator.CreateInstance的重载,将构造函数的参数也传入

//获取Student类的Type对象
Type StudentType = typeof(Student);
//创建实例
object StudentInstance = Activator.CreateInstance(StudentType,"张三",15);

使用程序集内的类创建对象,只要正确加载DLL就行

//加载程序集
byte[] assemblyBytes = File.ReadAllBytes(@"F:\GIT项目\me-and-mine--net\C#\C#-Projects\C# Learing Project\dll\ClassLibrary.dll");
Assembly ToolAssembly = AppDomain.CurrentDomain.Load(assemblyBytes);
//获取Student类的Type对象
Type StudentType = typeof(Student);
//创建实例
object StudentInstance = Activator.CreateInstance(StudentType,"张三",15);

反射读取与赋值对象属性

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Student()
    {
        Name = "";
        Age = 0;
    }

    public Student(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public  void ShowInfo()
    {
        Console.WriteLine($"My Name is {Name},I'm   {Age} years old. ");
    }
}

赋值Name属性

//获取Student类的Type对象
Type StudentType = typeof(Student);
//创建实例
object StudentInstance = Activator.CreateInstance(StudentType, "张三", 15);
//获取Name属性对象
PropertyInfo NamePropertyInfo = StudentType.GetProperty("Name");
//设置Name属性的值
NamePropertyInfo.SetValue(StudentInstance, "李四", null);

读取Name属性

2种方式

//打印属性
Console.WriteLine(NamePropertyInfo.GetValue(StudentInstance, null));
//转成dynamic,读取属性
Console.WriteLine(((dynamic)StudentInstance).Name);

反射读取程序集内类的方法名称和参数类型

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Student()
    {
        Name = "";
        Age = 0;
    }

    public Student(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public  void ShowInfo()
    {
        Console.WriteLine($"My Name is {Name},I'm   {Age} years old. ");
    }
}
//加载程序集
Assembly ToolAssembly = Assembly.Load("ClassLibrary");
//获取Student类的Type对象
Type StudentType = typeof(Student);

foreach (var method in StudentType.GetMethods())
{
    //输出方法名
    Console.WriteLine($"参数方法名:{method.Name}");
    //输出参数名称和类型
    foreach (var param in method.GetParameters())
    {
        Console.WriteLine($"参数名称:{param.Name},参数类型:{param.ParameterType}");
    }
    Console.WriteLine("");
}

 输出

参数方法名:get_Name

参数方法名:set_Name
参数名称:value,参数类型:System.String

参数方法名:get_Age

参数方法名:set_Age
参数名称:value,参数类型:System.Int32

参数方法名:ShowInfo

参数方法名:GetType

参数方法名:ToString

参数方法名:Equals
参数名称:obj,参数类型:System.Object

参数方法名:GetHashCode

  

 

反射调用方法

对象的方法

//加载程序集
Assembly ToolAssembly = Assembly.Load("ClassLibrary");
//获取Student类的Type对象
Type StudentType = typeof(Student);
//获取Student类中的ShowInfo()方法对象
MethodInfo methodInfo = StudentType.GetMethod("ShowInfo");
//调用,这是个动态类,需要指定对象
methodInfo.Invoke(StudentInstance,null);

 

静态类的方法

public static class Tool
{
    /// <summary>
    /// 求和a+b
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    public static int AddNumber(int a, int b)
    {
        return a + b;
    }

}
Type ToolType = typeof(Tool);
MethodInfo ToolMethodInfo = ToolType.GetMethod("AddNumber");
// 创建参数数组
object[] parameters = new object[] { 3, 4 };
Console.WriteLine($"3+4={ToolMethodInfo.Invoke(null, parameters)}");
//输出3+4=7

 

 

反射创建泛型类对象

泛型类对象创建前,需要将泛型对象类转化为指定类型的类。

public class MyGenericClass<T>
{
    public T Value { get; set; }

    public MyGenericClass(T value)
    {
        Value = value;
    }
}

class MainApplication
{

    static void Main()
    {
        // 1. 获取泛型类型定义
        Type type = typeof(MyGenericClass<>);
        // 2. 构造具体的泛型类型
        Type specificType = type.MakeGenericType(typeof(int));
        // 3. 创建实例
        object s = Activator.CreateInstance(specificType,12);
        // 输出实例的值
        Console.WriteLine(((dynamic)s).Value);
    }
}

 

 

 

反射调用泛型方法

普通类的泛型方法

public static class Tool
{
    /// <summary>
    /// 普通类里的泛型方法
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="W"></typeparam>
    /// <typeparam name="X"></typeparam>
    /// <param name="valueT"></param>
    /// <param name="valueW"></param>
    /// <param name="valueX"></param>
    public static void ShowInfo<T,W,X>(T valueT, W valueW, X valueX)
    {
        Console.WriteLine(valueT);
        Console.WriteLine(valueW);
        Console.WriteLine(valueX);
    }
}
Type GenericType = typeof(Tool);
MethodInfo GenericMethodInfo = GenericType.GetMethod("ShowInfo");
//指定方法参数类型
var GenericMethod = GenericMethodInfo.MakeGenericMethod(new Type[] { typeof(int),typeof(string),typeof(DateTime)});
//调用方法
GenericMethod.Invoke(null,new object[] { 3, "hello", DateTime.Now });

 

泛型类的泛型方法

namespace ClassLibrary.Generic
{
    public class MyGenericClass<T,W,S>
    {
        public  void  ShowInfo<X, Y, Z>(X valueT, Y valueW,Z valueX)
        {
            Console.WriteLine(valueT);
            Console.WriteLine(valueW);
            Console.WriteLine(valueX);
        }

    }
}
Type GenericType = ToolAssembly.GetType("ClassLibrary.Generic.MyGenericClass`3").MakeGenericType(new Type[] { typeof(int), typeof(string), typeof(DateTime) });//因为是3个泛型参数,拿占位符才能取到实际名称

object Generic = Activator.CreateInstance(GenericType);

MethodInfo GenericMethodInfo = GenericType.GetMethod("ShowInfo").MakeGenericMethod(new Type[] { typeof(int), typeof(string), typeof(DateTime) });

GenericMethodInfo.Invoke(Generic, new object[] { 3, "hello", DateTime.Now });

  

 

注意事项

- 反射的性能开销相对较大,因此应该谨慎使用,特别是在性能敏感的应用中。

- 使用反射访问私有成员可能破坏封装性,应该避免除非有充分的理由。

- 反射可能会导致安全风险,因为它绕过了编译时的类型检查。

 

反射调用方法
对象的方法
 
 
posted @ 2024-11-06 09:17  予我心安A3  阅读(637)  评论(0)    收藏  举报