C#进阶-反射的详解与应用

news2024/12/22 18:16:58

一、反射的概念

反射是.NET框架提供的一个功能强大的机制,它允许程序在运行时检查和操作对象的类型信息。通过使用反射,程序可以动态地创建对象、调用方法、访问字段和属性,无需在编译时显式知道类型信息。在.NET中,所有类型的信息最终都是存储在元数据中的。反射就是.NET提供的一组API,允许我们在运行时访问这些元数据,从而获得关于程序集、模块、类型、成员等的详细信息。

反射概念图:

在这里插入图片描述


二、反射的应用

反射的应用非常广泛,包括动态类型创建、动态方法调用、属性访问、自定义属性处理等。
我们可以根据反射的对象不同,分为两类:字段反射和方法反射。

1、字段反射

字段反射是指在运行时使用反射API来访问和修改对象的字段。这在需要动态访问对象的内部字段时非常有用,尤其是在不具有对象类型显式知识的情况下。

① 获取字段值

假设我们想要获取一个对象的字段值,可以使用FieldInfo.GetValue方法。仍然以User类为例,假设我们想获取Name字段的值。

举个例子:

using System;

public class User
{
    public string Name = "Initial Name";
}

class Program
{
    static void Main()
    {
        User user = new User();
        Type userType = typeof(User);
        var fieldName = "Name";
        
        // 获取User类的Name字段
        var fieldInfo = userType.GetField(fieldName);
        
        // 获取User实例的Name字段值
        var value = fieldInfo.GetValue(user);
        
        Console.WriteLine(value); // 输出: Initial Name
    }
}

可以看到我们通过反射的方式,将Name属性的值成功输出。


② 修改字段值

假设有一个User类,包含一个Name字段。我们想要在运行时修改某个User实例的Name字段值。

举个例子:

using System;

public class User
{
    public string Name;
}

class Program
{
    static void Main()
    {
        User user = new User();
        Type userType = typeof(User);
        var fieldName = "Name";
        
        // 获取User类的Name字段
        var fieldInfo = userType.GetField(fieldName);
        
        // 设置User实例的Name字段值
        fieldInfo.SetValue(user, "Damon");
        
        Console.WriteLine(user.Name); // 输出: Damon
    }
}

上述代码演示了如何使用字段反射来动态修改User实例的Name字段。首先,通过typeof(User)获取User类型的Type对象。然后,使用Type对象的GetField方法获取Name字段的FieldInfo对象。最后,使用FieldInfo对象的SetValue方法来修改字段的值。


③ 检查字段属性

反射还允许我们检查字段的属性,例如判断字段是否为公有(Public)、私有(Private)、静态(Static)等。这可以通过FieldInfo对象的属性来实现,例如IsPublicIsPrivateIsStatic等。

举个例子:

using System;

public class User
{
    public string Name;
    private int age;
    public static string Category = "General";
}

class Program
{
    static void Main()
    {
        Type userType = typeof(User);
        
        // 获取并检查字段属性
        var publicField = userType.GetField("Name");
        Console.WriteLine($"Name is Public: {publicField.IsPublic}"); // 输出: True
        
        var privateField = userType.GetField("age", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        Console.WriteLine($"Age is Private: {privateField.IsPrivate}"); // 输出: True
        
        var staticField = userType.GetField("Category");
        Console.WriteLine($"Category is Static: {staticField.IsStatic}"); // 输出: True
    }
}

在上述代码示例中,我们展示了如何使用通过FieldInfo对象的属性来实现分类。User类定义了一个公有字段Name和一个私有字段age。通过反射,我们能够获取并打印出这些字段的公有或私有信息。


④ 使用BindingFlags枚举

BindingFlags枚举用于指定控制反射的绑定和搜索方式。在使用Type.GetFieldType.GetFields方法时,可以通过BindingFlags来精确控制要检索的字段类型(如公有/私有、静态/实例等)。

举个例子:

using System;
using System.Reflection;

public class User
{
    public string Name = "Damon";
    private int age = 30;
}

class Program
{
    static void Main()
    {
        Type userType = typeof(User);
        
        // 使用BindingFlags枚举获取所有公有字段
        var publicFields = userType.GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach (var field in publicFields)
        {
            Console.WriteLine($"Public Field: {field.Name}");
        }
        
        // 使用BindingFlags枚举获取所有私有字段
        var privateFields = userType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
        foreach (var field in privateFields)
        {
            Console.WriteLine($"Private Field: {field.Name}");
        }
    }
}

通过这个例子,我们可以看到BindingFlags枚举在使用反射进行成员访问时的强大能力。它允许开发者以非常精确的方式指定想要访问的成员类型和访问模式,无论这些成员是公有的、私有的、静态的还是实例的。这种灵活性使得BindingFlags在处理复杂反射场景时成为不可或缺的工具。


2、方法反射

方法反射允许在运行时动态地调用类型的方法。这对于实现插件架构、调用不确定或未知方法特别有用。

举个例子:

using System;

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

class Program
{
    static void Main()
    {
        Calculator calc = new Calculator();
        Type calcType = typeof(Calculator);
        
        // 获取Add方法的信息
        var methodInfo = calcType.GetMethod("Add");
        
        // 动态调用Add方法
        object[] parameters = new object[] { 10, 20 };
        var result = methodInfo.Invoke(calc, parameters);
        
        Console.WriteLine(result); // 输出: 30
    }
}

在这个例子中,我们首先创建了Calculator类的一个实例。接着,通过typeof(Calculator)获取Calculator类型的Type对象。然后,使用Type对象的GetMethod方法获取Add方法的MethodInfo对象。最后,我们使用MethodInfo对象的Invoke方法动态地调用Add方法,并传入参数。

这种方法的强大之处在于,我们不需要在编译时明确知道Calculator类的实现细节,就能够在运行时调用其方法。这在处理插件或者需要大量反射的框架时尤其有用。

在方法反射的应用中,除了简单地调用方法之外,还可以用于更复杂的场景,如调用带有不同参数的方法、访问私有方法或者调用泛型方法等。下面我们通过一些例子来展示方法反射的这些高级用法。


① 调用有参方法

假设我们有一个Calculator类,它有一个方法Add,这个方法接受两个int类型的参数,并返回它们的和。我们可以使用反射来调用这个方法,即使我们在编译时不知道这个方法的存在。

举个例子:

using System;

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

class Program
{
    static void Main()
    {
        Calculator calc = new Calculator();
        Type calcType = typeof(Calculator);
        
        // 获取Add方法
        var methodInfo = calcType.GetMethod("Add");
        
        // 调用Add方法,传入参数
        var result = methodInfo.Invoke(calc, new object[] { 10, 20 });
        
        Console.WriteLine($"10 + 20 = {result}");
    }
}

在这个例子中,我们首先实例化了Calculator类。然后,通过使用typeof(Calculator)获得Calculator类型的Type对象,我们利用GetMethod获取名为Add的方法的MethodInfo对象。通过MethodInfo对象的Invoke方法,我们可以动态地调用Add方法,并传递两个整数作为参数,最后打印出这两个整数的和。


② 访问私有方法

在某些情况下,你可能需要调用一个类的私有方法。通过反射,可以实现这一点,即使这通常被认为是破坏封装原则的行为。

举个例子:

using System;
using System.Reflection;

public class Messenger
{
    private void DisplayMessage(string message)
    {
        Console.WriteLine($"Message: {message}");
    }
}

class Program
{
    static void Main()
    {
        Messenger messenger = new Messenger();
        Type messengerType = messenger.GetType();
        
        // 获取私有方法
        var methodInfo = messengerType.GetMethod("DisplayMessage", BindingFlags.NonPublic | BindingFlags.Instance);
        
        // 调用私有方法
        methodInfo.Invoke(messenger, new object[] { "Hello, Reflection!" });
    }
}

这里,我们定义了一个Messenger类,其中包含一个私有方法DisplayMessage。在Main方法中,我们创建了Messenger的一个实例,并通过调用GetType方法获得其类型对象。然后,我们使用GetMethod方法并配合BindingFlags.NonPublic | BindingFlags.Instance参数来获取私有方法的MethodInfo对象。有了这个对象,我们就可以使用Invoke方法来调用DisplayMessage,即使它是私有的。


③ 调用泛型方法

反射还允许调用泛型方法。这在处理需要在运行时确定泛型类型参数的场景下非常有用。

举个例子:

using System;
using System.Reflection;

public class Utility
{
    public void Print<T>(T message)
    {
        Console.WriteLine($"Message: {message}");
    }
}

class Program
{
    static void Main()
    {
        Utility utility = new Utility();
        Type utilityType = typeof(Utility);
        
        // 获取泛型方法的原始定义
        var methodInfo = utilityType.GetMethod("Print").MakeGenericMethod(new Type[] { typeof(string) });
        
        // 调用泛型方法
        methodInfo.Invoke(utility, new object[] { "Hello, Generic Reflection!" });
    }
}

在此例中,Utility类包含一个泛型方法Print<T>,它接受一个类型为T的参数,并将其打印到控制台。在Main方法中,我们首先实例化了Utility类。使用GetMethod获取到Print方法的MethodInfo对象后,我们通过MakeGenericMethod方法指定泛型方法的具体类型。在这个例子中,我们将T指定为string类型。最后,我们使用Invoke方法来调用Print方法,传递了一个字符串作为参数。

这种方法特别有用,因为它允许在运行时决定泛型方法的类型参数,从而提高了代码的灵活性和通用性。


④ 调用带有输出参数的方法

有时候,你可能需要调用的方法包含输出(out)参数。使用反射调用这样的方法时,你也可以获取输出参数的值。

举个例子:

using System;
using System.Reflection;

public class Converter
{
    public bool TryParse(string input, out int result)
    {
        return int.TryParse(input, out result);
    }
}

class Program
{
    static void Main()
    {
        Converter converter = new Converter();
        Type converterType = typeof(Converter);
        
        // 获取方法信息
        var methodInfo = converterType.GetMethod("TryParse");
        
        // 创建参数数组,包括输入和输出参数
        object[] parameters = new object[] { "123", null };
        
        // 调用方法
        var success = (bool)methodInfo.Invoke(converter, parameters);
        
        // 获取输出参数的值
        int parsedValue = (int)parameters[1];
        
        if (success)
        {
            Console.WriteLine($"Parsing successful: {parsedValue}");
        }
        else
        {
            Console.WriteLine("Parsing failed.");
        }
    }
}

这个例子中,我们定义了一个Converter类,其中包含一个方法TryParse,这个方法尝试将一个字符串转换为整数,并通过输出参数返回转换结果。在调用这个方法时,我们首先准备了一个参数数组parameters,其中第一个元素是输入字符串,第二个元素是用于接收输出值的占位符(初始化为null)。调用Invoke方法后,输出参数的值被填充到了parameters数组的相应位置,我们可以通过索引访问并使用这个值。

这种调用方法对于处理需要输出参数的方法非常有用,尤其是在动态场景下,它允许开发者在运行时与方法的输入和输出交互,增加了代码的灵活性。


⑤ 调用重载方法

在有些情况下,一个类可能有多个同名方法(即方法重载)。使用反射调用特定的重载版本时,可以通过指定参数类型来获取正确的MethodInfo对象。

举个例子:

using System;
using System.Reflection;

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public double Add(double a, double b)
    {
        return a + b;
    }
}

class Program
{
    static void Main()
    {
        Calculator calc = new Calculator();
        Type calcType = typeof(Calculator);

        // 指定要调用的重载方法的参数类型
        Type[] paramTypes = { typeof(int), typeof(int) };
        MethodInfo methodInfo = calcType.GetMethod("Add", paramTypes);

        // 调用重载方法
        var result = methodInfo.Invoke(calc, new object[] { 10, 20 });

        Console.WriteLine($"10 + 20 = {result}");
    }
}

在这个例子中,Calculator类有两个Add方法的重载版本:一个接受两个int类型的参数,另一个接受两个double类型的参数。为了调用特定的重载版本(在这里是接受int参数的版本),我们在GetMethod调用中提供了一个表示参数类型的Type数组。这样,就可以准确地获取到所需的MethodInfo对象,并通过Invoke方法调用它。


三、反射的使用场景

① 类型检查和元数据访问

这一类应用涉及到在运行时获取类型的信息,如类的名称、方法、属性、字段等。通过元数据访问,程序可以动态地获取和操作类型信息,实现高度的灵活性。

  • 获取类型信息:包括类名、命名空间、继承层次结构等。
  • 成员访问:访问和操作字段、属性、方法、事件等。

② 动态对象创建和方法调用

反射最直观的用途之一是动态地创建对象和调用方法。这使得开发者可以在不知道对象确切类型的情况下,进行对象的实例化和方法调用。

  • 动态对象创建:通过类型名称动态创建对象实例。
  • 动态方法执行:在运行时调用方法,包括公有、私有方法和重载方法的调用。

③ 动态代理和拦截

反射可以用来实现动态代理和方法拦截,这在很多高级编程场景中非常有用,比如实现AOP(面向切面编程)。

  • 动态代理:创建一个对象的代理,代理对象可以在目标对象的方法调用前后执行额外的逻辑。
  • 方法拦截:拦截对特定方法的调用,可以用于日志记录、性能监测、事务处理等。

④ 自定义属性(Attribute)处理

反射允许程序检查代码中的自定义属性,这是实现各种框架(如测试框架、ORM框架等)的基础。

  • 属性读取:读取类、方法、字段等上的自定义属性,用于配置或特殊处理。
  • 属性驱动的逻辑:基于自定义属性执行特定逻辑,如序列化/反序列化、数据库操作等。

⑤ 动态代码生成和编译

利用反射,结合表达式树(Expression Trees)或其他动态代码生成技术,可以在运行时生成和编译代码。这对于需要大量动态性的应用非常有用。

  • 动态代码生成:生成新的方法或类定义。
  • 运行时编译:将动态生成的代码编译成可执行代码。

反射的应用覆盖了从基础的类型探查到复杂的动态代理和代码生成等高级场景,为开发高度灵活和动态的应用程序提供了强大的支持。每种应用场景都展示了反射机制如何使得代码能够在运行时适应和响应不同的需求,从而实现高度的灵活性和动态性。


四、反射总结

反射是C#中一个非常强大的特性是C#高级编程中不可或缺的一部分,了解和掌握反射的使用可以帮助开发者编写更加灵活和强大的.NET应用程序。它提供了一种在运行时查询和操作类型信息的能力,通过反射,我们可以动态地创建对象、调用方法、访问字段和属性,这为编写灵活和动态的代码提供了极大的便利。

尽管反射提供了强大的功能,但它也有一些缺点。反射操作通常比直接代码调用要慢,因为它需要在运行时解析类型信息。此外,过度使用反射可能会使代码变得难以理解和维护。因此,我们应该谨慎使用,在使用反射时应该权衡其给项目带来的好处和成本,避免不必要的性能开销和复杂性增加。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1547710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录训练营第58天 | LeetCode 739. 每日温度、​​​​​​LeetCode 496.下一个更大元素 I

目录 LeetCode 739. 每日温度 文章讲解&#xff1a;代码随想录(programmercarl.com) 视频讲解&#xff1a;单调栈&#xff0c;你该了解的&#xff0c;这里都讲了&#xff01;LeetCode:739.每日温度_哔哩哔哩_bilibili 思路 ​​​​​​LeetCode 496.下一个更大元素 I 文…

【pytest、playwright】构建POM项目,以及解决登录问题,allure环境问题

目录 前言 1、文件目录 2、安装依赖 3、POM项目实战-案例&#xff1a;打开指定页面 目录结构&#xff1a; pages中的代码&#xff1a; cases中的代码&#xff1a; 4、解决登录问题 问题&#xff1a; 解决方案&#xff1a; 获取登录的用户信息&#xff08;cookie&a…

静态住宅IP优缺点,究竟要怎么选?

在进行海外 IP 代理时&#xff0c;了解动态住宅 IP 和静态住宅 IP 的区别以及如何选择合适的类型非常重要。本文将介绍精态住宅 IP 特点和&#xff0c;并提供选择建议&#xff0c;帮助您根据需求做出明智的决策。 静态住宅 IP 的特点 静态住宅 IP 是指 IP 地址在一段时间内保…

[C++]内联函数(内联函数的概念,内联函数的特性,内联函数与宏的区别)

一、内联函数的概念 以inline修饰的的函数叫内联函数&#xff0c;编译时C编译器会在调用内联函数的位置将内联函数展开&#xff0c;内联函数没有调用函数参数压栈的开销&#xff0c;内联函数可以提高程序的运行效率。 例子&#xff1a; 没有使用内联函数 使用内联函数&#xff…

STM32技术打造:智能考勤打卡系统 | 刷卡式上下班签到自动化解决方案

文章目录 一、简易刷卡式打卡考勤系统&#xff08;一&#xff09;功能简介原理图设计程序设计 哔哩哔哩&#xff1a; https://www.bilibili.com/video/BV1NZ421Y79W/?spm_id_from333.999.0.0&vd_sourcee5082ef80535e952b2a4301746491be0 一、简易刷卡式打卡考勤系统 &…

UE4_旋转节点总结一

一、Roll、Pitch、Yaw Roll 围绕X轴旋转 飞机的翻滚角 Pitch 围绕Y轴旋转 飞机的俯仰角 Yaw 围绕Z轴旋转 飞机的航向角 二、Get Forward Vector理解 测试&#xff1a; 运行&#xff1a; 三、Get Actor Rotation理解 运行效果&#xff1a; 拆分旋转体测试一&a…

警惕垃圾邮件,伪造法院传真传播Sodinokibi勒索病毒

Sodinokibi勒索病毒在国内首次被发现于2019年4月份&#xff0c;2019年5月24日首次在意大利被发现&#xff0c;在意大利被发现使用RDP攻击的方式进行传播感染&#xff0c;这款病毒被称为GandCrab勒索病毒的接班人&#xff0c;在GandCrab勒索病毒运营团队停止更新之后&#xff0c…

Can‘t resolve ‘mockjs‘ in ‘......

问题场景&#xff1a; 未从根本目录打开项目在运行npm run serve 后报错&#xff1a;Parsing error: No Babel config file detected for...... 解决方法&#xff1a;在终端 cd ./含有package.json的文件夹/ npm run serve 此时在加载到70%之后报错 Cant resolve mockjs in .…

政安晨:【Keras机器学习实践要点】(四)—— 顺序模型

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras实战演绎机器学习 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 介绍 Keras是一个用于构建和训练深度学习模…

Docker搭建LNMP环境实战(05):CentOS环境安装Docker-CE

前面几篇文章讲了那么多似乎和Docker无关的实战操作&#xff0c;本篇总算开始说到Docker了。 1、关于Docker 1.1、什么是Docker Docker概念就是大概了解一下就可以&#xff0c;还是引用一下百度百科吧&#xff1a; Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以…

初探Notion安装与使用

笔记工具哪家强&#xff0c;有道云笔记&#xff0c;印象笔记&#xff0c;记事本&#xff0c;notion 第一步、下载与安装 本次选择是window版本&#xff0c;下载地址【Notion官网】 版本为Notion Setup 3.3.0&#xff0c;软件大小74.3M&#xff0c;官网如下图所示。 进入登录…

STM32之HAL开发——DMA转运串口数据

DMA功能框图&#xff08;F1系列&#xff09; 如果外设要想通过 DMA 来传输数据&#xff0c;必须先给 DMA 控制器发送 DMA 请求&#xff0c; DMA 收到请求信号之后&#xff0c;控制器会给外设一个应答信号&#xff0c;当外设应答后且 DMA 控制器收到应答信号之后&#xff0c;就会…

[深度学习]yolov8+pyqt5搭建精美界面GUI设计源码实现四

【简单介绍】 经过精心设计和深度整合&#xff0c;我们成功推出了这款融合了先进目标检测算法YOLOv8与高效PyQt5界面开发框架的目标检测GUI界面软件。该软件在直观性、易用性和功能性方面均表现出色&#xff0c;为用户提供了高效稳定的操作体验。 在界面设计方面&#xff0c;…

1.5T数据惨遭Lockbit3.0窃取,亚信安全发布《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件93起&#xff0c;近三周攻击数量呈现持平状态。 本周Lockbit3.0是影响最严重的勒索家族&#xff0c;Blacksuit和Ransomhub恶意家族紧随其后&#xff0c;从整体上看Lockbit3.0依旧是影响最严重的勒索家族&#xff0c;需要注意防范。 …

MT6762_联发科MTK6762安卓核心板规格参数

MTK6762核心板是一款集成了蓝牙、fm、wlan和gps模块的高度集成基带平台&#xff0c;为LTE/LTE-A和C2K智能手机应用程序提供支持。该安卓核心板集成了ARM Cortex-A53处理器&#xff0c;工作频率可达2.0GHz&#xff0c;并且还集成了功能强大的多标准视频编解码器。除此之外&#…

如何区分模型文件是稳定扩散模型和LORA模型

区分模型文件是否为稳定扩散模型&#xff08;Stable Diffusion Models&#xff09;或LORA模型&#xff08;LowRank Adaptation&#xff09;通常需要对模型的结构和内容有一定的了解。以下是一些方法来区分这两种模型文件&#xff1a; 1. 文件格式和结构 稳定扩散模型&#xff1…

软考 系统架构设计师系列知识点之云原生架构设计理论与实践(7)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之云原生架构设计理论与实践&#xff08;6&#xff09; 所属章节&#xff1a; 第14章. 云原生架构设计理论与实践 第2节 云原生架构内涵 14.2 云原生架构内涵 关于云原生的定义有众多版本&#xff0c;对于云原生架构的…

使用npm仓库的优先级以及.npmrc配置文件的使用

使用npm仓库的优先级以及.npmrc配置文件的使用 概念如何设置 registry&#xff08;包管理仓库&#xff09;1. 设置项目配置文件2. 设置用户配置文件3. 设置全局配置文件4. .npmrc文件可以配置的常见选项 概念 npm&#xff08;Node Package Manager&#xff09;是一个Node.js的…

PanTools v1.0.17 多网盘批量管理 批量分享、转存、复制...

软件介绍 一款针对多个热门网盘的文件管理、批量分享、批量转存、批量复制、批量重命名、批量链接检测、跨账号移动文件、多账号文件搜索等&#xff0c;支持不同网盘的不同账号的资源文件操作。适用于网站站长、资源爱好者等&#xff0c;对于管理名下具有多个网盘多个账号具有…

CSS 实现毛玻璃效果 | backdrop-filter与filter的区别

CSS 毛玻璃效果是一种使用 CSS 创建的视觉效果&#xff0c;可以使元素或区域呈现出模糊或半透明的外观&#xff0c;就像毛玻璃一样。 示例&#xff1a; <div class"container"><div class"textHolder"><p>glass</p></div>…