第二单元 反射
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.separatorChar
是static
。令人惊讶的是,有一种解决此问题的方法:只需通过反射使 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 的问题。我只展示如何。 鼠标悬停在否决票上说,“这个答案没用”。对不起,但我觉得你的回答符合这个标准。您的回答表明可以更改最终值,而实际上并非如此。内联值不会更改。结果是完全不可预测的。我很欣赏分享这些深奥的知识,但永远不应该按照你的建议去做。 @NateSstatic 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 运行时环境二进制代码许可。以上是关于第二单元 反射的主要内容,如果未能解决你的问题,请参考以下文章