C# - 反射
C# - 反射
Section titled “C# - 反射”反射提供了一种强大的机制,用于在运行时检查类型、对象和程序集的元数据。使用 System.Reflection 命名空间中的类,你可以动态发现有关已加载程序集的信息,实例化类型,并调用它们的成员(方法、属性、字段)。
警告: 反射是一个强大但高级的功能。它比直接的、编译时检查的代码要慢,并且可能使代码更难理解和维护。请谨慎使用它。
反射的现代应用
Section titled “反射的现代应用”尽管你可能不会在日常应用程序代码中直接使用反射,但它却是许多现代框架和工具背后的核心引擎:
- 序列化库: 诸如
Newtonsoft.Json或System.Text.Json等库使用反射来发现对象的公共属性,以便将其转换为 JSON 或从 JSON 转换回来。 - 依赖注入(DI)容器: DI 框架扫描程序集以查找服务及其依赖项,然后使用反射在运行时构建和注入对象。
- 对象关系映射器(ORM): 像 Entity Framework Core 这样的工具使用反射将类属性映射到数据库表列。
- 单元测试框架: 测试运行器使用反射查找标有
[TestMethod]或[Fact]等属性的方法,以将其作为测试执行。 - 插件架构: 应用程序可以加载外部程序集(DLL),并使用反射查找和实例化实现特定插件接口的类型。
获取类型信息
Section titled “获取类型信息”反射的入口点是 System.Type 对象。你可以通过以下几种方式获取它:
using System;using System.Reflection;
// 1. 使用 typeof 运算符(在编译时)Type stringType = typeof(string);
// 2. 在实例上使用 GetType() 方法(在运行时)var greeting = "Hello, World!";Type instanceType = greeting.GetType();
// 3. 使用 Type.GetType() 静态方法(通过字符串名称)// 注意:对于当前程序集之外的类型,需要完全限定名。Type listType = Type.GetType("System.Collections.Generic.List`1");
Console.WriteLine($"Type Name: {stringType.Name}");Console.WriteLine($"Is Public: {stringType.IsPublic}");Console.WriteLine($"Full Name: {stringType.FullName}");检查类型成员
Section titled “检查类型成员”获得 Type 对象后,你可以使用 GetMethods()、GetProperties() 等方法来探索其构造函数、方法、属性和字段。你可以使用 BindingFlags 来过滤结果(例如,包含私有或静态成员)。
示例:列出类的成员
Section titled “示例:列出类的成员”using System;using System.Reflection;using System.Linq;
public class User{ public int Id { get; set; } public string Name { get; set; } private string _passwordHash;
public User(string name) { Name = name; }
public void Greet() => Console.WriteLine($"Hello, {Name}!");
private bool IsPasswordValid(string pass) => true;}
public class Program{ public static void Main() { Type userType = typeof(User); Console.WriteLine($"--- Analysis of Type: {userType.Name} ---");
// 获取公共属性 var properties = userType.GetProperties(); Console.WriteLine("\nPublic Properties:"); foreach (var prop in properties) { Console.WriteLine($"- {prop.Name} (Type: {prop.PropertyType.Name})"); }
// 仅获取此类型中声明的公共方法 var methods = userType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) .Where(m => !m.IsSpecialName); // 排除属性的 get/set 方法 Console.WriteLine("\nPublic Methods:"); foreach (var method in methods) { Console.WriteLine($"- {method.Name}"); }
// 获取私有字段 var privateFields = userType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); Console.WriteLine("\nPrivate Fields:"); foreach (var field in privateFields) { Console.WriteLine($"- {field.Name}"); } }}执行后,该程序会输出:
--- Analysis of Type: User ---
Public Properties:- Id (Type: Int32)- Name (Type: String)
Public Methods:- Greet
Private Fields:- _passwordHash动态调用成员
Section titled “动态调用成员”反射还允许你创建类型的实例并动态调用它们的方法。
示例:创建实例并调用方法
Section titled “示例:创建实例并调用方法”using System;using System.Reflection;
public class Greeter{ public void SayHello(string name) => Console.WriteLine($"Hello, {name} from reflection!");}
public class Program{ public static void Main() { Type greeterType = typeof(Greeter);
// 创建 Greeter 类的实例 object greeterInstance = Activator.CreateInstance(greeterType);
// 获取 'SayHello' 方法的 MethodInfo MethodInfo sayHelloMethod = greeterType.GetMethod("SayHello");
// 在实例上调用带参数的方法 if (sayHelloMethod != null) { sayHelloMethod.Invoke(greeterInstance, new object[] { "Alice" }); } else { Console.WriteLine("Method 'SayHello' not found."); } }}执行后,该程序会输出:
Hello, Alice from reflection!反射的一个常见用途是检查自定义特性。这允许你向代码添加声明性元数据,然后可以在运行时查询这些元数据以改变行为。
示例:读取自定义特性
Section titled “示例:读取自定义特性”using System;using System.Reflection;
// 1. 定义一个自定义特性[AttributeUsage(AttributeTargets.Method)]public class TestDescriptionAttribute : Attribute{ public string Description { get; } public TestDescriptionAttribute(string description) => Description = description;}
// 2. 应用特性public class MyTests{ [TestDescription("This test validates the login functionality.")] public void Test_UserLogin_Succeeds() { /* ... */ }}
// 3. 使用反射读取特性public class Program{ public static void Main() { var methodInfo = typeof(MyTests).GetMethod("Test_UserLogin_Succeeds"); var attribute = methodInfo.GetCustomAttribute<TestDescriptionAttribute>();
if (attribute != null) { Console.WriteLine($"Test Description: {attribute.Description}"); } }}输出:
Test Description: This test validates the login functionality.性能与现代替代方案
Section titled “性能与现代替代方案”- 性能开销:反射涉及基于字符串的查找并绕过编译时检查,使其比直接代码慢得多。避免在性能关键的循环中使用它。
- 源生成器:对于许多常见的反射场景(如序列化或 DI),现代 .NET 正在转向源生成器。这些是编译时工具,可以生成快速、显式的 C# 代码,让你在获得动态行为的同时避免反射的运行时性能开销。
- 泛型和接口:在使用反射之前,请考虑你的问题是否可以通过泛型、接口或抽象基类更清晰、更安全地解决。
dynamic关键字:对于 COM 互操作或处理动态语言,dynamic关键字可以是反射的更简单替代方案,尽管它也存在性能开销。