Skip to content

C# - 反射

反射提供了一种强大的机制,用于在运行时检查类型、对象和程序集的元数据。使用 System.Reflection 命名空间中的类,你可以动态发现有关已加载程序集的信息,实例化类型,并调用它们的成员(方法、属性、字段)。

警告: 反射是一个强大但高级的功能。它比直接的、编译时检查的代码要慢,并且可能使代码更难理解和维护。请谨慎使用它。

尽管你可能不会在日常应用程序代码中直接使用反射,但它却是许多现代框架和工具背后的核心引擎:

  • 序列化库: 诸如 Newtonsoft.Json 或 System.Text.Json 等库使用反射来发现对象的公共属性,以便将其转换为 JSON 或从 JSON 转换回来。
  • 依赖注入(DI)容器: DI 框架扫描程序集以查找服务及其依赖项,然后使用反射在运行时构建和注入对象。
  • 对象关系映射器(ORM): 像 Entity Framework Core 这样的工具使用反射将类属性映射到数据库表列。
  • 单元测试框架: 测试运行器使用反射查找标有 [TestMethod] 或 [Fact] 等属性的方法,以将其作为测试执行。
  • 插件架构: 应用程序可以加载外部程序集(DLL),并使用反射查找和实例化实现特定插件接口的类型。

反射的入口点是 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}");

获得 Type 对象后,你可以使用 GetMethods()、GetProperties() 等方法来探索其构造函数、方法、属性和字段。你可以使用 BindingFlags 来过滤结果(例如,包含私有或静态成员)。

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

反射还允许你创建类型的实例并动态调用它们的方法。

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!

反射的一个常见用途是检查自定义特性。这允许你向代码添加声明性元数据,然后可以在运行时查询这些元数据以改变行为。

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.
  • 性能开销:反射涉及基于字符串的查找并绕过编译时检查,使其比直接代码慢得多。避免在性能关键的循环中使用它。
  • 源生成器:对于许多常见的反射场景(如序列化或 DI),现代 .NET 正在转向源生成器。这些是编译时工具,可以生成快速、显式的 C# 代码,让你在获得动态行为的同时避免反射的运行时性能开销。
  • 泛型和接口:在使用反射之前,请考虑你的问题是否可以通过泛型、接口或抽象基类更清晰、更安全地解决。
  • dynamic 关键字:对于 COM 互操作或处理动态语言,dynamic 关键字可以是反射的更简单替代方案,尽管它也存在性能开销。