第二单元 反射

Posted 誉尚学教育

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第二单元 反射相关的知识,希望对你有一定的参考价值。

1. 透过现象看本质

反射被誉为是 c#中的黑科技 ,在很多领域中都有反射的身影,例如,我们经常使用的ORM框架,ABP框架 等。

反射指程序可以访问、检测和修改它本身状态或行为的一种能力。. 程序集包含模块,而模块包含类型,类型又包含成员。. 反射则提供了封装程序集、模块和类型的对象。. 您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。. 然后,可以调用类型的方法或访问其字段和属性(除了私有构造)。.

 

准备好一个StudentInfo 类,供后面使用

public class StudentInfo

    public void Run()
    
        Console.WriteLine($"我是NickName,我每天都要晨跑");
    
​
    public void Run2(int age)
    
        Console.WriteLine($"我是NickName,我今年age,我每天都要晨跑");
    
​
    private string Run3(string nickName)
    
        return $"我是nickName,我是私有方法";
    
​
​
    public StudentInfo()
    
​
    
    public StudentInfo(string nickName,int age)
    
        NickName = nickName;
        Age = age;
    
​
    private string _studentNo;//字段
    private int Id  get; set; //属性
    public int Age  get; set; 
    public string NickName  get; set; 

 

2. Type声明

表示类型声明:类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。这个类是线程安全的。 Type 为 System.Reflection 功能的根,也是访问元数据的主要方式。使用 Type 的成员获取关于类型声明的信息,如构造函数、方法、字段、属性和类的事件,以及在其中部署该类的模块和程序集。 有两种方法可以生成Type类的对象:一种是Typeof(类名),一种是对象调用GetType()函数。

// 方式一
Type type = typeof(StudentInfo);

// 方式二
StudentInfo stu = new();
Type type = stu.GetType();

3. 详细知识点

1. 获取属性

//1.要获取类型
var propList = typeof(StudentInfo).GetProperties();//获取所有的属性
foreach (var propertyInfo in propList)

    Console.WriteLine(propertyInfo.Name);

 

2. 获取字段

//印证了:属性是对字段的封装
var fields = typeof(StudentInfo).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var fieldInfo in fields)

    Console.WriteLine(fieldInfo.Name);

 

3. 获取类的命名空间

var type = typeof(StudentInfo);
var nameSpace = type.Namespace;
Console.WriteLine($"命名空间:nameSpace");

//获取类名
Console.WriteLine($"类名:type.Name");
//类的完整名称:命名空间+类名
Console.WriteLine($"类的完整名称:type.FullName");

Console.WriteLine($"它的基类:type.BaseType");

Console.WriteLine($"验证委托是否是类:typeof(Action).IsClass");

 

4. 创建对象

var tp = typeof(StudentInfo);
// 底层其实是调用了 无参构造方法。 对象创建的唯一途径只能是构造方法被调用
var obj = Activator.CreateInstance(tp);

// 再次的验证CreateInstance 其实是调用了构造方法
var instance = Activator.CreateInstance(type1,"任我行",18) as StudentInfo;

 

5. 操作属性

//1.第一步获取类型
var tp = typeof(StudentInfo);
//获取某个属性
var prop = tp.GetProperty("NickName");

//StudentInfo stu = new StudentInfo("张三",19);
//stu.NickName = "任我行";
//var nickName = stu.NickName;
//2.通过反射创建对象(其实调用的是构造函数)
var obj = Activator.CreateInstance(tp);
prop.SetValue(obj, "李四");
var val = prop.GetValue(obj);//获取属性的值
Console.WriteLine(val);

6. 操作私有字段

var type = typeof(Person);
var obj = Activator.CreateInstance(tp);
var fieldInfos = type.GetFields(BindingFlags.Instance|BindingFlags.NonPublic);
fieldInfos[0].SetValue(obj,10);

var val = fieldInfos[0].GetValue(obj);
Console.WriteLine(val);

7. 获取程序集信息

//加载程序集名称  程序必须在当前Bin目录下
var assembly1 = Assembly.Load("ClassLibrary1");  

//路径加载程序集名称
var assembly2 = Assembly.LoadFile(@“d:\\路径\\ ClassLibrary1 .dll");

//加载程序集后创建对象
Object obj =  assembly1.CreateInstance(“命名空间.类名“,false); 

//获取程序集完整路径
string location = Assembly.GetExecutingAssembly().Location;

//获取程序集名称
string file = Assembly.GetExecutingAssembly().GetName().Name;

 //获取程序集版本号
string version =  Assembly.GetExecutingAssembly().GetName().Version.ToString();

8. 操作构造函数

私有构造函数不可被操作。

var type = typeof(StudentInfo);

// 获取构造方法 StudentInfo(string nickName,int age)
var constructorInfo = type.GetConstructor(new []typeof(string),typeof(int));
// 根据构造方法,创建对象
var student = (StudentInfo)constructorInfo.Invoke(new object?[]"任我行",18);
Console.WriteLine($"姓名=student.NickName,年龄=student.Age");

 

9. 操作方法

var type = typeof(StudentInfo);

// 获取构造方法 StudentInfo(string nickName,int age)
var constructorInfo = type.GetConstructor(new []typeof(string),typeof(int));
// 根据构造方法,创建对象
var student = constructorInfo.Invoke(new object?[]"任我行",18);

// 获取所有的方法,包括私有方法 与 继承至 Object 对象的方法
var methodInfos = type.GetMethods(BindingFlags.Instance|BindingFlags.NonPublic);

// 获取指定方法
var runMethod = type.GetMethod("Run");
// 第二个参数表示被调用的方法参数,null 表示此方法为无参方法
runMethod.Invoke(student, null); 

 

10. 操作特性

自定义一个MyDescriptionAttribute特性,在StudentInfo 类上 加上一个特性 MyDescriptionAttribute

[AttributeUsage(AttributeTargets.All)]
public class MyDescriptionAttribute:Attribute

    public string Name  get; set; 



[MyDescription("学生类的描述信息,我是个学生类")]
public class StudentInfo

     // .... 其他信息省略

var type = typeof(StudentInfo);
var desc = type.GetCustomAttribute<MyDescriptionAttribute>(false);
Console.WriteLine(desc.Name);

 

配套视频链接:

C# 高级编程,.Net6 系列 开发第三阶段,学完拿捏你的面试官,.net6 进阶学习(已完结)_哔哩哔哩_bilibili

使用反射更改静态最终 File.separatorChar 进行单元测试?

【中文标题】使用反射更改静态最终 File.separatorChar 进行单元测试?【英文标题】:Using reflection to change static final File.separatorChar for unit testing? 【发布时间】:2011-01-29 06:18:15 【问题描述】:

具体来说,我正在尝试为需要使用File.separatorChar 在 Windows 和 unix 上构建路径的方法创建单元测试。代码必须在两个平台上运行,但是当我尝试更改这个静态 final 字段时,我遇到了 JUnit 错误。

有人知道发生了什么吗?

Field field = java.io.File.class.getDeclaredField( "separatorChar" );
field.setAccessible(true);
field.setChar(java.io.File.class,'/');

当我这样做时,我得到

IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character

想法?

【问题讨论】:

最好在其他目标操作系统下的 VirtualBox 环境中运行单元测试。谁知道当你像这样弄乱 JVM 时会发生什么。此外,也许可以重写您的代码以不直接使用 File.separatorChar。例如,您可以使用 File(parentFile, name) 构造函数构建路径。 @Thilo:这是个好主意,现在我想起来了,可能有一种方法可以跨平台运行我的逻辑,而无需处理文件 URL。但是,我认为知道如何更改 java.io.File.separatorChar 对于其他一些合法用例来说是一件有用的事情。 【参考方案1】:

来自Field.set 的文档:

如果基础字段是最终字段,则该方法将抛出 IllegalAccessException,除非 setAccessible(true) 已成功处理此字段并且此字段是非静态的

所以一开始你似乎不走运,因为File.separatorCharstatic。令人惊讶的是,一种解决此问题的方法:只需通过反射使 static 字段不再是 final

我改编了这个解决方案from javaspecialist.eu:

static void setFinalStatic(Field field, Object newValue) throws Exception 
    field.setAccessible(true);

    // remove final modifier from field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(null, newValue);

我已经测试过了,它可以工作:

setFinalStatic(File.class.getField("separatorChar"), '#');
System.out.println(File.separatorChar); // prints "#"

对这种技术要格外小心。撇开毁灭性的后果不谈,以下实际上是有效的:

setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"

重要更新:上述解决方案并非适用于所有情况。如果该字段在重置之前可以访问并通过反射读取,则会引发IllegalAccessException。它失败是因为反射 API 创建了内部 FieldAccessor 对象,这些对象被缓存和重用(请参阅 java.lang.reflect.Field#acquireFieldAccessor(boolean) 实现)。 失败的示例测试代码:

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException

【讨论】:

#define FALSE 1 又回来了,它已经准备好重新开始。 天哪! setAccessible 的引入是个大问题。 @NateS:没有人说这是个好主意。我们正在调查是否可行。显然是的。这不是拒绝我的理由。不要射击信使。如果您对我们为什么这样做有疑问,请否决 OP 的问题。我只展示如何。 鼠标悬停在否决票上说,“这个答案没用”。对不起,但我觉得你的回答符合这个标准。您的回答表明可以更改最终值,而实际上并非如此。内联值不会更改。结果是完全不可预测的。我很欣赏分享这些深奥的知识,但永远不应该按照你的建议去做。 @NateS static final 编译时常量可以被 javac 内联。显然这里不是这样。这是尝试测试可能会改变的东西。虽然在运行时 final 字段可以内联,但实际上这对于单元测试来说应该不是问题。【参考方案2】:

尝试调用文件的实例而不是类 File 的实例

例如

File file = ...;    
field.setChar(file,'/');

您也可以尝试http://code.google.com/p/jmockit/ 并模拟静态方法 FileSystem.getFileSystem()。 (不知道您是否可以模拟静态变量,通常不需要这些 hack -> 编写 oo 代码并使用“仅”模拟)

【讨论】:

【参考方案3】:

在构建文件时,只需在任何地方使用 /。我已经这样做了 13 年,从来没有遇到过问题。也没有什么可测试的。

【讨论】:

【参考方案4】:

我知道这并不能直接回答您的问题,但 Apache Commons FileNameUtils 将进行跨平台文件名构造,并且可以节省您编写自己的类来执行此操作。

【讨论】:

我正在解构路径,而不是构建路径,但我可以做的是将所有内容转换为 unix 分隔符,运行我的算法,然后转换为系统分隔符。通过这种方式,我可以在不需要模拟 java.io.File 的情况下进行测试。 我最终使用了这个解决方案,但下面的答案回答了我最初的问题,所以本着 *** 的精神,我接受了另一个答案。【参考方案5】:

这里我将为“android.os.Build.VERSION.RELEASE”设置值,其中 VERSION 是类名,RELEASE 是最终的静态字符串值。

如果基础字段是最终字段,则方法抛出 IllegalAccessException 所以我们需要使用 setAccessible(true) , 使用field.set()方法时需要添加NoSuchFieldException

@RunWith(PowerMockRunner.class)
@PrepareForTest(Build.VERSION.class)
public class RuntimePermissionUtilsTest 
@Test
public void hasStoragePermissions() throws IllegalAccessException, NoSuchFieldException 
    Field field = Build.VERSION.class.getField("RELEASE");
    field.setAccessible(true);
    field.set(null,"Marshmallow");
 

现在字符串 RELEASE 的值将返回“Marshmallow”。

【讨论】:

【参考方案6】:

不要使用 File.separatorChar 来声明你的服务类,我们称之为 PathBuilder 什么的。这个类将有一个 concatPaths() 方法,它将连接两个参数(使用操作系统的分隔符)。美妙之处在于您正在编写这个类,因此您可以在对它进行单元测试时随意调整它。

【讨论】:

【参考方案7】:

您可以获取 java.io.File 的源,并对其进行修改,使 separatorChar 和 separator 不是最终的,并添加一个 setSeparatorChar 方法来更新它们两者,然后将编译后的类包含在您的引导类路径中。

【讨论】:

您可能必须将该功能扩展到所有 java.io.FileSystem 实现。 注意:不应部署使用此选项来覆盖 rt.jar 中的类的应用程序,因为这样做会违反 Java 2 运行时环境二进制代码许可。

以上是关于第二单元 反射的主要内容,如果未能解决你的问题,请参考以下文章

Go基础之单元测试反射网络编程操作Redis操作MySQL

Junit单元测试,反射,注解 annotation

Junit单元测试,反射,注解 annotation

Junit单元测试反射注解

Java基础-单元测试反射注解篇

基础-Junit单元测试_反射_注解