C# 反射(Reflection)超详细解析

0.应用程序结构

应用程序结构:
在这里插入图片描述

1.反射概念

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

反射概念图:
在这里插入图片描述

2.反射用途

System.Reflection 命名空间下

作用
Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法来调用特定的方法。
PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
FieldInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
MemberInfo用于获取有关类 (构造函数、事件、字段、方法和属性) 的所有成员的信息。此类引入所有成员提供的基本功能。
EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors或 GetConstructor方法来调用特定的构造函数。
ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。

3.System.Type 类

通过这个类可以访问任何给定数据类型的信息。 System.Type 类对于反射起着核心的作用。但它是一个抽象的基类,Type有与每种数据类型对应的派生类,我们使用这个派生类的对象的方法、字段、属性来查找有关该类型的所有信息。
生成Type对象:

  • 使用typeof运算符
// typeof(x)中的x,必须是具体的类名、类型名称等,不可以是变量名称。
Type t = typeof(int);
  • 使用GetType()方法
// GetType()方法继承自Object,所以C#中任何对象都具有GetType()方法
int i;
Type t = i.GetType();
  • 使用Type类的静态方法GetType()
Type t = Type.GetType("System.Double");

属性:

属性描述
Name数据类型名
FullName数据类型的完全限定名(包括命名空间名)
Namespace定义数据类型的命名空间名
IsAbstract指示该类型是否是抽象类型
IsArray指示该类型是否是数组
IsClass指示该类型是否是类
IsEnum指示该类型是否是枚举
IsInterface指示该类型是否是接口
IsPublic指示该类型是否是公有的
IsSealed指示该类型是否是密封类
IsValueType指示该类型是否是值类型

方法:

方法描述
GetConstructor(),GetConstructors()返回ConstructorInfo类型,用于取得该类的构造函数的信息
GetEvent(), GetEvents()返回EventInfo类型,用于取得该类的事件的信息
GetField(), GetFields()返回FieldInfo类型,用于取得该类的字段(成员变量)的信息
GetInterface(),GetInterfaces()返回InterfaceInfo类型,用于取得该类实现的接口的信息
GetMember(), GetMembers()返回MemberInfo类型,用于取得该类的所有成员的信息
GetMethod(), GetMethods()返回MethodInfo类型,用于取得该类的方法的信息
GetProperty(), GetProperties()返回PropertyInfo类型,用于取得该类的属性的信息

4.System.Reflection.Assembly 类

方法描述
Load(String)通过程序集名称返回Assembly对象
LoadFrom(String)通过DLL文件名称返回Assembly对象
LoadFile(String)加载指定路径上的程序集文件的内容返回Assembly对象。
GetType(String)通过Assembly获取程序集中类,参数必须是类的全名
GetTypes通过Assembly获取程序集中所有的类
// 通过程序集名称返回Assembly对象
Assembly ass = Assembly.Load("MyClassLibrary");
// 通过DLL文件名称返回Assembly对象
Assembly ass = Assembly.LoadFrom("MyClassLibrary.dll");
// 通过Assembly获取程序集中类
Type t = ass.GetType("MyClassLibrary.NewClass");   //参数必须是类的全名
 // 通过Assembly获取程序集中所有的类
Type[] t = ass.GetTypes();

5.BindingFlags 指定反射查找范围

默认值:

// 默认值
Default

查找:
这些标记用于反射的时候查找类型成员:

// 表示查找的时候,需要忽略大小写。
IgnoreCase

// 仅查找此特定类型中声明的成员,而不会包括这个类继承得到的成员。
DeclaredOnly

// 仅查找类型中的实例成员。
Instance

// 仅查找类型中的静态成员。
Static

// 仅查找类型中的公共成员。
Public

// 仅查找类型中的非公共成员(internal protected private)
NonPublic

// 会查找此特定类型继承树上得到的静态成员。但仅继承公共(public)静态成员和受保护(protected)静态成员;不包含私有静态成员,也不包含嵌套类型。
FlattenHierarchy

调用:
这些标记用于为InvokeMember方法提供参数,告知应该如何反射调用一个方法:

// 调用方法。
InvokeMethod

// 创建实例。
CreateInstance

// 获取字段的值。
GetField

// 设置字段的值。
SetField

// 获取属性的值。
GetProperty

// 设置属性的值。
SetProperty

常用的组合:

  • 拿到所有成员
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance
  • 拿到公有的实例成员:
BindingFlags.Public | BindingFlags.Instance

6.使用案例

6.1 获取类的名字、命名空间、程序集

 //类的名称    
string name = peopleType.Name;         
Console.WriteLine(name); 
//类的命名空间
string space = peopleType.Namespace;
Console.WriteLine(space);  
//类的程序集    
Assembly assembly = peopleType.Assembly;
Console.WriteLine(assembly); 

/*
People
Ming
DesignPattern, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
*/

6.2 获取/修改类的字段、属性、方法、构造函数、事件等

BindingFlags flag = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;

People people = new People("ming", 18);
// 获取Type对象
Type peopleType = people.GetType();
// 获取name字段值
Console.WriteLine("------获取name字段值------");
FieldInfo nameField = peopleType.GetField("name", flag);
Console.WriteLine(nameField.GetValue(people));
// 设置name字段
Console.WriteLine("------设置name字段------");
nameField.SetValue(people, "ming123");
Console.WriteLine(nameField.GetValue(people));
// 获取所有字段
Console.WriteLine("------获取所有字段------");
FieldInfo[] fieldInfos = peopleType.GetFields(flag);
foreach (var field in fieldInfos)
{
    Console.WriteLine($"{field.Name}: {field.GetValue(people)}");
}
// 获取所有属性
Console.WriteLine("------获取所有属性------");
PropertyInfo[] propertyInfos = peopleType.GetProperties(flag);
foreach (var property in propertyInfos)
{
    Console.WriteLine($"{property.Name}: {property.GetValue(people)}");
}
// 获取所有方法
Console.WriteLine("------获取所有方法------");
MethodInfo[] methodInfos = peopleType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
foreach (var method in methodInfos)
{
    Console.WriteLine($"{method.Name}");
}
// 查看类构造函数
ConstructorInfo[] constructorInfos = peopleType.GetConstructors();    
foreach (ConstructorInfo constructor in constructorInfos)
{
    ParameterInfo[] ps = constructor.GetParameters();   //取出每个构造函数的所有参数
    foreach (ParameterInfo pi in ps)                    
    {
        Console.Write(pi.ParameterType.ToString() + " " + pi.Name + ",");
    }
}

/*
------获取name字段值------
ming
------设置name字段------
ming123
------获取所有字段------
name: ming123
age: 18
------获取所有属性------
Name: ming123
Age: 18
------获取所有方法------
get_Name
set_Name
get_Age
set_Age
SayHello
GetType
ToString
Equals
GetHashCode
------查看类构造函数------
System.String name,System.Int32 age,
*/

6.3 方法调用

6.3.1 调用无参函数

// 调用无参方法
MethodInfo method1 = peopleType.GetMethod("SayHello");
Console.WriteLine($"方法名:{method1.Name}");
Console.WriteLine($"返回值:{method1.ReturnType}");
method1.Invoke(people, null);
/*
方法名:SayHello
返回值:System.Void
ming123 say: Hello
*/

6.3.2 调用有参函数

People 新增方法

 public int Sum(int a, int b)
 {
     return a + b;
 }

调用

MethodInfo method2 = peopleType.GetMethod("Sum");
Console.WriteLine($"方法名:{method2.Name}");
Console.WriteLine($"返回值:{method2.ReturnType}");
foreach (var item in method2.GetParameters())
{
    Console.WriteLine($"参数名:{item.Name}");
    Console.WriteLine($"参数类型:{item.ParameterType}");
}
var sum = method2.Invoke(people, new object[]{1, 2});
Console.WriteLine(sum);

结果

方法名:Sum
返回值:System.Int32
参数名:a
参数类型:System.Int32
参数名:b
参数类型:System.Int32
3

6.3.3 调用私有方法

People 新增方法

private void PrintAge()
{
    Console.WriteLine(age);
}

调用

MethodInfo method3 = peopleType.GetMethod("PrintAge", BindingFlags.NonPublic | BindingFlags.Instance);
method3.Invoke(people, null);

结果

18

6.3.4 调用泛型方法People 新增方法

People 新增方法

public void GenericsMethod<T>(T parm)
{
    Console.WriteLine($"类型: {typeof(T)},值: {parm}");
}

调用

MethodInfo method4 = peopleType.GetMethod("GenericsMethod").MakeGenericMethod(new Type[]{typeof(string)});
MethodInfo method5 = peopleType.GetMethod("GenericsMethod").MakeGenericMethod(new Type[]{typeof(int)});

method4.Invoke(people, new object[]{"sting泛型"});
method5.Invoke(people, new object[]{123});

结果

类型: System.String,: sting泛型
类型: System.Int32,: 123

6.3.5 调用带有输出参数的方法(ref, out)

People 新增方法

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

调用

MethodInfo method6 = peopleType.GetMethod("TryParse");
// 创建参数数组,包括输入和输出参数
object[] parameters = new object[] { "123", null };
// 调用方法
var success = (bool)method6.Invoke(people, parameters);
// 获取输出参数的值
int parsedValue = (int)parameters[1];

if (success)
    Console.WriteLine($"Parsing successful: {parsedValue}");
else
    Console.WriteLine("Parsing failed.");

结果

Parsing successful: 123

6.3.6 调用重载方法

People 新增方法

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

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

调用

// 指定要调用的重载方法的参数类型
Type[] paramTypes1 = { typeof(int), typeof(int) };
Type[] paramTypes2 = { typeof(double), typeof(double) };

MethodInfo method7 = peopleType.GetMethod("Add", paramTypes1);
MethodInfo method8 = peopleType.GetMethod("Add", paramTypes2);

// 调用重载方法
var result1 = method7.Invoke(people, new object[] { 10, 20 });
var result2 = method8.Invoke(people, new object[] { 5.5, 6.5 });

Console.WriteLine($"10 + 20 = {result1}");
Console.WriteLine($"5.5 + 6.5 = {result2}");

结果

10 + 20 = 30
5.5 + 6.5 = 12

6.4 动态生成对象

6.4.1 用构造函数动态生成对象

// 根据参数类型获取构造函数 
ConstructorInfo constructor1 = peopleType.GetConstructor(new Type[]{ typeof(string), typeof(int) });
// 构造Object数组,作为构造函数的输入参数 
object[] obj = new object[2]{ "ming111", 22 };   
// 调用构造函数生成对象 
object o = constructor1.Invoke(obj);  
// 调用生成的对象的方法测试是否对象生成成功 
((People)o).SayHello();  

结果

ming111 say: Hello

6.4.2 用Activator生成对象

object obj1 = Activator.CreateInstance(peopleType, "ming222", 25);
((People)obj1).SayHello();  

结果

ming222 say: Hello

7.反射总结

  • 优点:
    反射是C#中一个非常强大的特性是C#高级编程中不可或缺的一部分,了解和掌握反射的使用可以帮助开发者编写更加灵活和强大的.NET应用程序。它提供了一种在运行时查询和操作类型信息的能力,通过反射,我们可以动态地创建对象、调用方法、访问字段和属性,这为编写灵活和动态的代码提供了极大的便利。
  • 缺点:
    尽管反射提供了强大的功能,但它也有一些缺点。反射操作通常比直接代码调用要慢,因为它需要在运行时解析类型信息。此外,过度使用反射可能会使代码变得难以理解和维护。因此,我们应该谨慎使用,在使用反射时应该权衡其给项目带来的好处和成本,避免不必要的性能开销和复杂性增加。
  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C# 反射是指在运行时动态地获取类的信息,通过反射可以获取类的属性、方法、事件等信息,以及动态创建对象、调用方法、获取值等操作。这使得我们能够在运行时动态地编写代码,可以写出更加灵活和可扩展的程序。 C# 反射的核心是 `System.Reflection` 命名空间,该命名空间包含了许多与反射相关的类和接口,比如 `Type` 类、`MethodInfo` 类、`PropertyInfo` 类等。 下面是一些常用的反射操作: 1. 获取类型信息 可以使用 `typeof` 关键字或者 `Type.GetType()` 方法来获取类型的信息。`typeof` 关键字用于获取编译时已知的类型信息,而 `Type.GetType()` 方法则可以通过类型名称获取运行时的类型信息。 ```csharp // 获取 System.String 类型的信息 Type type1 = typeof(System.String); // 获取指定类型名称的信息 Type type2 = Type.GetType("System.String"); ``` 2. 获取成员信息 可以使用 `Type.GetMembers()` 方法获取类型的所有成员信息,包括属性、方法、字段、事件等。也可以使用 `Type.GetMethod()`、`Type.GetProperty()`、`Type.GetField()`、`Type.GetEvent()` 等方法获取指定成员的信息。 ```csharp Type type = typeof(Person); // 获取类型的所有成员信息 MemberInfo[] members = type.GetMembers(); // 获取指定属性的信息 PropertyInfo property = type.GetProperty("Name"); // 获取指定方法的信息 MethodInfo method = type.GetMethod("SayHello"); // 获取指定字段的信息 FieldInfo field = type.GetField("Age"); // 获取指定事件的信息 EventInfo eventInfo = type.GetEvent("PropertyChanged"); ``` 3. 动态创建对象 可以使用 `Activator.CreateInstance()` 方法动态创建对象,也可以使用 `Type.InvokeMember()` 方法调用构造函数来创建对象。 ```csharp Type type = typeof(Person); // 使用 Activator.CreateInstance() 方法创建对象 Person person1 = (Person)Activator.CreateInstance(type); // 使用 Type.InvokeMember() 方法调用构造函数创建对象 Person person2 = (Person)type.InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] { "Tom", 18 }); ``` 4. 调用成员 可以使用 `MethodInfo.Invoke()` 方法调用方法,也可以使用 `PropertyInfo.SetValue()` 方法设置属性的值,使用 `FieldInfo.SetValue()` 方法设置字段的值。 ```csharp Type type = typeof(Person); Person person = new Person("Tom", 18); // 调用方法 MethodInfo method = type.GetMethod("SayHello"); method.Invoke(person, null); // 设置属性的值 PropertyInfo property = type.GetProperty("Name"); property.SetValue(person, "Jerry", null); // 设置字段的值 FieldInfo field = type.GetField("Age"); field.SetValue(person, 20); ``` 以上是 C# 反射的一些基本操作,反射的应用非常广泛,可以用来实现插件式开发、ORM 映射等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值