序号 | 系列文章 |
---|---|
11 | 【C#基础】C# 预处理器指令 |
12 | 【C#基础】C# 文件与IO |
13 | 【C#进阶】C# 特性 |
文章目录
- 前言
- 1,反射的概念
- 2,使用反射访问特性
- 3,反射的用途
- 4,反射的优缺点比较
- 4.1 优点:
- 4.2 缺点:
- 5,System.Reflection 命名空间
- 5.1 获取程序集中的信息
- 5.2 获取程序集中的类型
- 5.3 获取程序集中的成员
- 5.4 创建类型的实例对象
- 结语
前言
✋ 大家好,我是writer桑,本章为大家介绍 C# 中的反射。
1,反射的概念
反射指的是程序可以访问,检测和修改它本身状态或行为的一种行为。 其中访问的目标包括程序集1、模块和类型对象等。可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问器字段和属性。反射行为也常常用来访问应用在程序上的特性。
代码示例:(简单展示)
using System;
using System.Reflection;
public class Example
{
static void Main()
{
// 使用反射获取程序集的信息:
Assembly info = typeof(int).Assembly;
Console.WriteLine(info);
}
}
运行结果:
在上例中,使用 typeof 方法获取已加载的 int 程序集的完整名称。
2,使用反射访问特性
在前面的一章中,我们讨论到了 C# 使用反射访问特性的操作。在本章中就可以使用反射的操作来访问 Example 类中的元数据。
代码示例:
using System;
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct)
]
public class AuthorAttribute : System.Attribute
{
public string name;
public double age;
// 构造函数
public AuthorAttribute(string name)
{
this.name = name;
age = 11;
}
}
[Author("writer桑", age = 21)]
public class Example
{
// Example类成员
}
public class Program
{
static void Main()
{
// 访问 Example 类应用的特性
Type type = typeof(Example);
// 遍历输出
foreach(Object attributes in type.GetCustomAttributes(true))
{
AuthorAttribute author = (AuthorAttribute)attributes;
Console.WriteLine(author.age);
Console.WriteLine(author.name);
}
}
}
运行结果:
3,反射的用途
反射的用途可以总结为以下几点:
- 需要访问程序中的元数据的特性时。(点击了解更多)
- 检查和实例化程序集中的类型时。
- 在运行时构建新的类型,比如使用 System.Reflection.Emit 中的类。(点击了解更多)
- 执行后期绑定,访问在运行时创建的类型上的方法时。(点击了解更多)
4,反射的优缺点比较
反射的优缺点比较:
4.1 优点:
- 反射是运行期的操作,提高了程序的灵活性和扩展性。
- 降低耦合度2,提高自适应能力。
- 允许动态的创建和使用对象,无需提前硬编码3目标对象。
4.2 缺点:
- 性能较低,反射是运行期4的代码,在性能方面不如直接的编译型5代码。
- 可读性较低,使用反射会使得程序本身的逻辑变得复杂,在可读性方面不如直接的代码来的简洁。
- 维护成本变高,反射是一种绕过了源代码的技术, 因而在维护的方面难度较高。
5,System.Reflection 命名空间
System.Reflection 命名空间中包含通过检测托管代码中程序集、模块、成员、参数和其他实体的元数据来检索其相关信息的类型。同时也可以用于操作加载类型的实例,例如钩子函数6或调用方法。在 C# 中,实现反射操作常常需要用到 System.Reflection 命名空间中的类和方法。
列举一些 System.Reflection 命名空间中常用的反射操作:
5.1 获取程序集中的信息
System.Reflection 命名空间中的 Assembly.FullName 等属性可以用来获取程序集中的信息。
代码示例:
using System;
using System.Reflection;
public class Example
{
private int factor;
public Example(int f)
{
factor = f;
}
public int SampleMethod(int x)
{
Console.WriteLine($"实例方法的执行:({x})");
return x * factor;
}
public static void Main()
{
Assembly assem = typeof(Example).Assembly;
Console.WriteLine($"程序集的全程:{assem.FullName}");
// 可以使用 AssemblyName 类型解析完整名称。
AssemblyName assemName = assem.GetName();
Console.WriteLine($"名称: {assemName.Name}");
//从程序集创建一个对象,并传入正确的数字
//构造函数的参数类型。
Object o = assem.CreateInstance("Example", false, BindingFlags.ExactBinding, null, new Object[] { 2 }, null, null);
// 对对象的实例方法进行晚绑定调用。
MethodInfo m = assem.GetType("Example").GetMethod("SampleMethod");
Object ret = m.Invoke(o, new Object[] { 42 });
Console.WriteLine($"示例方法的返回值为:{ret}");
Console.WriteLine($"程序集入口点:{assem.EntryPoint}");
}
}
运行结果:
5.2 获取程序集中的类型
System.Reflection 命名空间中的 Assembly.GetExportedTypes 方法可以用来获取程序集中定义的公共类型,这些公共类型在程序集外可见。
代码示例:
using System;
using System.Reflection;
public class PublicClass
{
public class PublicNestedClass { }
protected class ProtectedNestedClass { }
internal class FriendNestedClass { }
private class PrivateNestedClass { }
}
public class Example
{
public static void Main()
{
// 获取程序集中定义的公共类型
foreach (Type t in typeof(Example).Assembly.GetExportedTypes())
{
Console.WriteLine(t);
}
}
}
运行结果:
5.3 获取程序集中的成员
System.Reflection 命名空间中的 MemberInfo 类中的属性和方法可以用来获取有关成员属性的信息并提供对成员元数据的访问权限。
代码示例:
using System;
using System.Reflection;
public class Example
{
// 显示应用到指定成员的自定义属性
public static void DisplayAttributes(Int32 indent, MemberInfo mi)
{
// 获取自定义属性集;如果不存在,则返回。
object[] attrs = mi.GetCustomAttributes(false);
if (attrs.Length == 0) { return; }
// 显示应用于该成员的自定义属性。
Display(indent + 1, "属性:");
foreach (object o in attrs)
{
Display(indent + 2, "{0}", o.ToString());
}
}
// 显示一个缩进的格式化字符串。
public static void Display(Int32 indent, string format, params object[] param)
{
Console.Write(new string(' ', indent * 2));
Console.WriteLine(format, param);
}
public static void Main()
{
//该变量保存缩进的数量, 显示每一行信息时使用。
Int32 indent = 0;
// 显示加载到这个 AppDomain 第一个 程序集的信息。
Assembly b = AppDomain.CurrentDomain.GetAssemblies()[0];
Display(indent, "程序集: {0}", b);
// 显示从此程序集导出的 第一个 类型的相关信息。
indent += 1;
Type t = b.GetExportedTypes()[0];
Display(0, "");
Display(indent, "类型: {0}", t);
// 遍历显示成员及其自定义属性。
indent += 1;
foreach (MemberInfo mi in t.GetMembers()) // GetMembers 方法
{
Display(indent, "成员: {0}", mi.Name);
DisplayAttributes(indent, mi);
// 如果成员是一个方法,显示它的参数信息。
if (mi.MemberType == MemberTypes.Method)
{
foreach (ParameterInfo pi in ((MethodInfo)mi).GetParameters())
{
Display(indent + 1, "参数: 类型={0}, 名字={1}", pi.ParameterType, pi.Name);
}
}
// 如果成员是一个属性,显示关于属性的访问方法的信息。
if (mi.MemberType == MemberTypes.Property)
{
foreach (MethodInfo am in ((PropertyInfo)mi).GetAccessors())
{
Display(indent + 1, "访问器方法: {0}", am);
}
}
}
}
}
运行结果:
5.4 创建类型的实例对象
System.Reflection 空间中的 Assembly.CreateInstance 方法可以用来获取包含当前执行的代码的程序集,以此来创建类型的实例对象。
代码示例:
using System;
using System.Reflection;
public class Person
{
private string _name;
public Person()
{ }
public Person(string name)
{
this._name = name;
}
public string Name
{
get { return this._name; }
set { this._name = value; }
}
public override string ToString()
{
return this._name;
}
}
public class Example
{
public static void Main()
{
Assembly assem = typeof(Person).Assembly;
// 创建 Person 类的实例化对象
Person p = (Person)assem.CreateInstance("Person");
if (!(p == null))
{
p.Name = "John";
Console.WriteLine($"实例化值为'{p}'的{p.GetType().Name}对象");
}
else
{
Console.WriteLine("无法实例化Person对象。");
}
}
}
运行结果:
点击了解更多 System.Reflection 命名空间的使用。
结语
👋 以上就是关于 C# 反射的介绍啦,希望对大家有所帮助。感谢大家的支持。
程序集(assembly):是一个及一个以上托管模块,以及一些资源文件的逻辑组合。在 .NET 中,dll与exe文件都是程序集。 ↩︎
耦合性(或称耦合力或耦合度):是一种软件度量,是指一程序中,模块及模块之间信息或参数依赖的程度。 ↩︎
硬编码:是将数据直接嵌入到程序或其他可执行对象的源代码中的软件开发实践,与从外部获取数据或在运行时生成数据不同。 ↩︎
运行期:是把编译后的文件交给计算机执行,直到程序运行结束。 ↩︎
编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。 ↩︎
钩子函数:是 Windows 消息处理机制的一部分,是指在程序正常运行中接受信息之前预先启动的函数,用来检查和修改传给该程序的信息,(钩子)实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。 ↩︎