C#之反射🔥

1/18/2024 CSharp

# 什么是反射

在C#中,反射是指在运行时检查类型信息、获取类型成员(如字段、属性、方法等)并操作它们的过程。通过反射,你可以在程序运行时动态地获取、创建和调用类型的实例,而不需要在编译时知道这些类型的确切信息。反射提供了一种机制,允许你检查和操作程序集、类型和成员,从而实现更加灵活和动态的编程。例如,你可以使用反射来动态加载程序集、创建实例、调用方法、获取和设置属性值等。

# 反射创建对象

# 1.加载DLL

a.Assembly.LoadFrom: 这个方法会从指定的文件路径加载程序集,并返回表示加载的程序集的 Assembly 对象。 它会尝试解析程序集的依赖项,但会在应用程序的执行目录或 GAC (Global Assembly Cache) 之外查找程序集文件。 通常用于加载在运行时指定路径下的程序集。

Assembly assembly = Assembly.LoadFrom("EH.CameraLibrary.dll");//dll全名称,需要后缀
1

b.Assembly.LoadFile: 这个方法也会从指定的文件路径加载程序集,并返回表示加载的程序集的 Assembly 对象。 不同于 LoadFrom,LoadFile 不会尝试解析程序集的依赖项,而是只加载指定路径的程序集文件。 使用 LoadFile 可能会导致应用程序加载同一个程序集的多个版本,因为它不会检查程序集是否已经加载。

Assembly assembly = Assembly.LoadFile(@"D:\blog\Blog\blog\EH.CameraLibrary.dll");//dll文件全路径
1

c.Assembly.Load: 这个方法从给定的程序集名称加载程序集,并返回表示加载的程序集的 Assembly 对象。 它会在应用程序的执行目录、GAC以及应用程序域的加载上下文中查找程序集。 Load 方法的参数是 AssemblyName 对象,你可以指定程序集的名称、版本、文化等信息来加载程序集。

AssemblyName assemblyName = new AssemblyName("MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
Assembly.Load(assemblyName);
1
2

在选择使用哪个方法时,你需要考虑到程序集的依赖项情况以及加载程序集的目的。通常情况下,Assembly.LoadFrom 是最常用的,因为它能够解析 程序集的依赖项并且可以指定加载程序集的路径。不过,在某些情况下,Assembly.LoadFile 或 Assembly.Load 也可能更适合特定的需求。

# 2.获取类型

a.调用 Type 类的静态方法 GetType()

Type t = Type.GetType("EH.CameraLibrary.Person");//获取某一个具体的类型,参数需要是类的全名称
1

b.使用对象 GetType() 方法

Person person = new Person();
Type type = person.GetType();
1
2

c.使用typeof

Type t = typeof(Person);
1

# 创建对象

Type type = typeof(Person)
object instance = Activator.CreateInstance(type);
1
2

# 实战例子

namespace ReflectionDemo
{
	public class Person
	{
    public int Age { get; set; }
    public string Name { get; set; }
    public int Height {  get; set; }

    //无参静态方法
    public static void GetAge()
    {
      Console.WriteLine("StaticGetAge");
    }

    //无参静态方法
    public static void GetAge(int Age)
    {
      Console.WriteLine($"StaticGetAge:{Age}");
    }

    //空参无返回值
    public void GetInfo()
    {
        Console.WriteLine($"GetInfo--Name:{Name},Age:{Age},Height:{Height}");
    }

		//空参有返回值
		public string GetName()
    {
        return Name;
    }

		//有参无返回值
		public void SetAge(int age)
        {
			Age = age;
			Console.WriteLine($"SetAge--Name:{Name},Age:{Age},Height:{Height}");
		}

		//有参有返回值
		public int SetHeight(int height)
		{
			Height = height;
			Console.WriteLine($"SetHeight--Name:{Name},Age:{Age},Height:{Height}");
			return Height;
		}

		//自定义入参类型
		public void SetNameAndAge(PersonInfo personInfo)
		{
			Name = personInfo.Name;
			Age = personInfo.Age;
			Console.WriteLine($"SetNameAndAge--Name:{Name},Age:{Age},Height:{Height}");
		}

		//自定义入参出参类型
		public void SetNameAndHeight(PersonInfo personInfo,out PersonInfo person)
		{
			person = new PersonInfo();
			person.Name = personInfo.Name;
			person.Age = personInfo.Age;
			Console.WriteLine($"SetNameAndHeight--Name:{person.Name},Age:{person.Age}");
		}
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
namespace ReflectionDemo
{
	public class PersonInfo
	{
		public int Age { get; set; }
		public string Name { get; set; }
	}
}
1
2
3
4
5
6
7
8

# 方法调用

# 1.静态无参

// 创建 Person 实例
Person person = new Person();

// 获取 Person 类的 Type 对象
Type type = typeof(Person);

// 调用 GetAge静态方法(无参无返回值)
MethodInfo getAgeMethod = type.GetMethod("GetAge", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null);
getAgeMethod.Invoke(null,null);
1
2
3
4
5
6
7
8
9

# 2.静态有参

// 调用 GetAge静态方法(有参无返回值)
MethodInfo getAgeMethodHasParam = type.GetMethod("GetAge", BindingFlags.Public | BindingFlags.Static, null, new Type[] {typeof(int) }, null);
object[] getAgeParams = { 30 }; // 参数数组
getAgeMethodHasParam.Invoke(person, getAgeParams);
1
2
3
4

# 3.空参无返回值

// 调用 GetInfo 方法(空参无返回值)
MethodInfo getInfoMethod = type.GetMethod("GetInfo");
getInfoMethod.Invoke(person, null);
1
2
3

# 4.空参有返回值

// 调用 GetName 方法(空参有返回值)
MethodInfo getNameMethod = type.GetMethod("GetName");
string name = (string)getNameMethod.Invoke(person, null);
Console.WriteLine($"Name: {name}");
1
2
3
4

# 5.有参无返回值

// 调用 SetAge 方法(有参无返回值)
MethodInfo setAgeMethod = type.GetMethod("SetAge");
object[] setAgeParams = { 30 }; // 参数数组
setAgeMethod.Invoke(person, setAgeParams);
1
2
3
4

# 6.有参有返回值

// 调用 SetHeight 方法(有参有返回值)
MethodInfo setHeightMethod = type.GetMethod("SetHeight");
object[] setHeightParams = { 180 }; // 参数数组
int height = (int)setHeightMethod.Invoke(person, setHeightParams);
Console.WriteLine($"Height: {height}");
1
2
3
4
5

# 7.自定义入参类型

// 调用 SetNameAndAge 方法(自定义入参类型)
MethodInfo setNameAndAgeMethod = type.GetMethod("SetNameAndAge");
PersonInfo personInfo = new PersonInfo { Name = "John", Age = 25 }; // 自定义入参类型
object[] setNameAndAgeParams = { personInfo }; // 参数数组
setNameAndAgeMethod.Invoke(person, setNameAndAgeParams);
1
2
3
4
5

# 8.自定义入参出参类型

// 调用 SetNameAndHeight 方法(自定义入参出参类型)
MethodInfo setNameAndHeightMethod = type.GetMethod("SetNameAndHeight");
PersonInfo personInfoInOut = new PersonInfo { Name = "Alice", Age = 28 }; // 自定义入参类型
object[] setNameAndHeightParams = { personInfoInOut, null }; // 参数数组
setNameAndHeightMethod.Invoke(person, setNameAndHeightParams);
PersonInfo outputPersonInfo = (PersonInfo)setNameAndHeightParams[1];
Console.WriteLine($"Output Person Info: Name={outputPersonInfo.Name}, Age={outputPersonInfo.Age}");
Console.ReadLine()
1
2
3
4
5
6
7
8

# 属性赋值取值

// 获取 Person 类的 Type 对象
Type type = typeof(Person);
var instance = Activator.CreateInstance(type);
foreach (var prop in type.GetProperties())
{
    if (prop.Name.Equals("Name"))
    {
        prop.SetValue(instance, "张三");
    }
    else if (prop.Name.Equals("Height"))
    {
        prop.SetValue(instance, 170);
    }
    else if (prop.Name.Equals("Age"))
    {
        prop.SetValue(instance, 25);
    }                            
}                        
foreach (var prop in type.GetProperties())
{
    Console.WriteLine($"people.{prop.Name}={prop.GetValue(instance)}");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 反射的优劣

反射提供了一种动态探索和操作类型信息的强大机制,但它也有一些好处和坏处。

# 1.好处:

a.灵活性和动态性:反射允许在运行时动态地探索和操作类型信息,这使得编写更加灵活和动态的代码成为可能。它可以在编译时不知道类型的情况下进行操作。

b.插件和扩展性:通过动态加载程序集和创建类型实例,反射为实现插件架构和可扩展性提供了便利的方式。

c.元编程:反射使得程序可以在运行时检查和操作自身的结构,这为实现元编程(编写操作代码的代码)提供了可能。

d.适应复杂环境:当需要在程序运行时动态地适应不同的环境或配置时,反射可以提供一种解决方案。

# 2.坏处:

a.性能开销:反射通常比直接调用代码更慢,因为它涉及到动态查找和解析类型信息。因此,过度使用反射可能会导致性能问题。

b.类型安全问题:由于反射允许绕过编译时的类型检查,因此在使用反射时容易引入类型安全问题,例如调用不存在的成员或传递错误类型的参数。

c.可读性和维护性:使用反射的代码通常比直接调用代码更难以理解和维护,因为它在编写时不会暴露类型的结构和成员。

d.平台依赖性:某些反射操作可能会受到平台的限制,因此在跨平台开发时需要格外小心。

因此,在使用反射时需要权衡其带来的灵活性和动态性与可能的性能和可维护性问题,并谨慎地选择使用它的场景。