C# 反射(Reflection)
`Type`、动态创建与调用、属性读写、以及性能代价。
Key Points反射让程序在运行时检查并操作类型,代价是比直接调用慢 10~100 倍。
获取 Type
C#
using System;
using System.Reflection;
var t1 = typeof(string);
var t2 = "abc".GetType();
var t3 = Type.GetType("System.String");
Console.WriteLine(t1 == t2);
Console.WriteLine(t1.Name);
Console.WriteLine(t1.FullName);
Console.WriteLine(t1.IsClass);
Console.WriteLine(t1.IsValueType);
Console.WriteLine(typeof(int).IsValueType);Output
True
String
System.String
True
False
True查看成员
C#
using System;
using System.Linq;
using System.Reflection;
var t = typeof(List<int>);
Console.WriteLine(t.Name);
foreach (var ctor in t.GetConstructors())
{
Console.WriteLine("构造:" + string.Join(",", ctor.GetParameters().Select(p => p.ParameterType.Name)));
}
foreach (var m in t.GetMethods(BindingFlags.Public | BindingFlags.Instance).Take(5))
{
Console.WriteLine($"方法 {m.Name} -> {m.ReturnType.Name}");
}
foreach (var p in t.GetProperties())
{
Console.WriteLine($"属性 {p.Name} : {p.PropertyType.Name}");
}Output
List`1
构造:
构造:Int32
构造:IEnumerable`1
方法 Add -> Void
方法 AddRange -> Void
方法 Insert -> Void
方法 InsertRange -> Void
方法 RemoveAt -> Void
属性 Capacity : Int32
属性 Count : Int32
属性 Item : Int32动态创建与调用
C#
using System;
using System.Reflection;
var t = Type.GetType("System.Text.StringBuilder")!;
var sb = Activator.CreateInstance(t)!;
var append = t.GetMethod("Append", new[] { typeof(string) })!;
append.Invoke(sb, new object[] { "Hello" });
append.Invoke(sb, new object[] { ", World" });
var toString = t.GetMethod("ToString", Type.EmptyTypes)!;
Console.WriteLine(toString.Invoke(sb, null));Output
Hello, WorldActivator.CreateInstance builds the object, GetMethod finds the method and Invoke calls it. Pass the parameter type array to GetMethod or overloads throw AmbiguousMatchException.
读写属性
C#
using System;
using System.Reflection;
class User
{
public string Name { get; set; } = "张三";
public int Age { get; set; } = 28;
private string Secret { get; set; } = "hidden";
}
var u = new User();
var t = typeof(User);
foreach (var p in t.GetProperties())
{
var v = p.GetValue(u);
Console.WriteLine($"{p.Name} = {v}");
}
t.GetProperty("Age")!.SetValue(u, 30);
Console.WriteLine(u.Age);
// 私有成员也能读(需要 BindingFlags)
var secret = t.GetProperty("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine(secret?.GetValue(u));Output
Name = 张三
Age = 28
30
hiddenReflection reaches private members, bypassing encapsulation. Acceptable in tests to stub internal state; in production it signals a design problem — add a proper API instead.
性能代价
C#
using System;
using System.Diagnostics;
using System.Reflection;
class Target { public int Add(int a, int b) => a + b; }
var obj = new Target();
var method = typeof(Target).GetMethod("Add")!;
var args = new object[] { 1, 2 };
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1_000_000; i++) obj.Add(1, 2);
var direct = sw.ElapsedMilliseconds;
sw.Restart();
for (int i = 0; i < 1_000_000; i++) method.Invoke(obj, args);
var reflect = sw.ElapsedMilliseconds;
Console.WriteLine($"直接 {direct}ms 反射 {reflect}ms 倍数 {reflect / Math.Max(1, direct)}");Output
直接 2ms 反射 187ms 倍数 93| Approach | Speed | Use |
|---|---|---|
| 直接调用 | 1x | 默认 |
反射 Invoke | ~50-100x 慢 | 启动时一次性的装配 |
缓存 MethodInfo | 略好 | 避免重复查找 |
| 编译表达式树 | ~2x 慢 | 需要高频调用 |
dynamic | ~10x 慢 | 脚本、简单动态 |
| 源生成器 | 1x | 编译期生成代码 |
什么时候用反射
- 该用 —— 序列化框架(JSON/XML)、ORM、依赖注入容器、插件系统
- 该用 —— 单元测试里构造内部状态
- 不该用 —— 业务代码里按字符串调方法(用接口或委托)
- 不该用 —— 热路径(每秒上万次),除非缓存了委托
Modern .NET prefers source generators over runtime reflection: code is generated at build time with no startup cost and no AOT trimming issues.
System.Text.Json's JsonSerializerContext is exactly this.获取 Type
C#
using System;
using System.Reflection;
var t1 = typeof(string);
var t2 = "abc".GetType();
var t3 = Type.GetType("System.String");
Console.WriteLine(t1 == t2);
Console.WriteLine(t1.Name);
Console.WriteLine(t1.FullName);
Console.WriteLine(t1.IsClass);
Console.WriteLine(t1.IsValueType);
Console.WriteLine(typeof(int).IsValueType);Output
True
String
System.String
True
False
True查看成员
C#
using System;
using System.Linq;
using System.Reflection;
var t = typeof(List<int>);
Console.WriteLine(t.Name);
foreach (var ctor in t.GetConstructors())
{
Console.WriteLine("构造:" + string.Join(",", ctor.GetParameters().Select(p => p.ParameterType.Name)));
}
foreach (var m in t.GetMethods(BindingFlags.Public | BindingFlags.Instance).Take(5))
{
Console.WriteLine($"方法 {m.Name} -> {m.ReturnType.Name}");
}
foreach (var p in t.GetProperties())
{
Console.WriteLine($"属性 {p.Name} : {p.PropertyType.Name}");
}Output
List`1
构造:
构造:Int32
构造:IEnumerable`1
方法 Add -> Void
方法 AddRange -> Void
方法 Insert -> Void
方法 InsertRange -> Void
方法 RemoveAt -> Void
属性 Capacity : Int32
属性 Count : Int32
属性 Item : Int32动态创建与调用
C#
using System;
using System.Reflection;
var t = Type.GetType("System.Text.StringBuilder")!;
var sb = Activator.CreateInstance(t)!;
var append = t.GetMethod("Append", new[] { typeof(string) })!;
append.Invoke(sb, new object[] { "Hello" });
append.Invoke(sb, new object[] { ", World" });
var toString = t.GetMethod("ToString", Type.EmptyTypes)!;
Console.WriteLine(toString.Invoke(sb, null));Output
Hello, WorldActivator.CreateInstance 创建实例,GetMethod 找方法,Invoke 调用。注意 GetMethod 要传参数类型数组,否则同名重载会抛 AmbiguousMatchException。
读写属性
C#
using System;
using System.Reflection;
class User
{
public string Name { get; set; } = "张三";
public int Age { get; set; } = 28;
private string Secret { get; set; } = "hidden";
}
var u = new User();
var t = typeof(User);
foreach (var p in t.GetProperties())
{
var v = p.GetValue(u);
Console.WriteLine($"{p.Name} = {v}");
}
t.GetProperty("Age")!.SetValue(u, 30);
Console.WriteLine(u.Age);
// 私有成员也能读(需要 BindingFlags)
var secret = t.GetProperty("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine(secret?.GetValue(u));Output
Name = 张三
Age = 28
30
hidden反射能访问私有成员,这会绕过封装。测试里用它 mock 内部状态可以接受;生产代码里读私有字段说明设计有问题,应该改成正规的 API。
性能代价
C#
using System;
using System.Diagnostics;
using System.Reflection;
class Target { public int Add(int a, int b) => a + b; }
var obj = new Target();
var method = typeof(Target).GetMethod("Add")!;
var args = new object[] { 1, 2 };
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1_000_000; i++) obj.Add(1, 2);
var direct = sw.ElapsedMilliseconds;
sw.Restart();
for (int i = 0; i < 1_000_000; i++) method.Invoke(obj, args);
var reflect = sw.ElapsedMilliseconds;
Console.WriteLine($"直接 {direct}ms 反射 {reflect}ms 倍数 {reflect / Math.Max(1, direct)}");Output
直接 2ms 反射 187ms 倍数 93| 方式 | 速度 | 适用 |
|---|---|---|
| 直接调用 | 1x | 默认 |
反射 Invoke | ~50-100x 慢 | 启动时一次性的装配 |
缓存 MethodInfo | 略好 | 避免重复查找 |
| 编译表达式树 | ~2x 慢 | 需要高频调用 |
dynamic | ~10x 慢 | 脚本、简单动态 |
| 源生成器 | 1x | 编译期生成代码 |
什么时候用反射
- 该用 —— 序列化框架(JSON/XML)、ORM、依赖注入容器、插件系统
- 该用 —— 单元测试里构造内部状态
- 不该用 —— 业务代码里按字符串调方法(用接口或委托)
- 不该用 —— 热路径(每秒上万次),除非缓存了委托
现代 .NET 推荐用源生成器(Source Generator)替代运行时反射:编译期就生成好代码,没有启动开销也不受 AOT 裁剪影响。
System.Text.Json 的 JsonSerializerContext 就是这个思路。