什么是反射 (Reflection)

C# 的 反射 (Reflection) 是运行时读取、检查和操作类型(类、接口、结构)、成员(方法、属性、字段、构造函数)以及属性(Attribute)的一组 API,位于 System.Reflection 命名空间。简单说:反射能让程序在运行时“看见”自己和别的类型,并可以动态地创建对象、调用方法、读写属性、读取特性信息等。

常见用途

  • 动态创建对象(例如插件/依赖注入、序列化/反序列化)。
  • 运行时调用方法或访问属性(例如脚本、测试框架、ORM)。
  • 读取/处理自定义 Attribute(例如 MVC 的路由、验证特性)。
  • 构建工具或元编程(代码生成、检查、文档)。

API & 简单示例

  • Type: 类型信息
// 获取类型信息, 并创建实例
Type type1 = typeof(Sample);
object instance1 = Activator.CreateInstance(type1)!;
  • GetMethods: 获取所有方法
// 列出所有公共方法和静态方法
foreach (var m in type1.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
{
    Console.WriteLine($"  - {m.Name} (IsStatic={m.IsStatic}, Return={m.ReturnType.Name})");
}
  • GetMethod: 获取指定方法
// 获取 Print 方法, 传递 Hello 参数并调用
MethodInfo? print = type1.GetMethod("Print");
print?.Invoke(instance1, new object?[] { "Hello" });
  • GetProperty: 获取指定属性
// 获取 PublicProperty 属性, 并获取/设置值
PropertyInfo? prop = type1.GetProperty("PublicProperty");
Console.WriteLine($"{prop?.Name}: {prop?.GetValue(instance1, null)}");
prop?.SetValue(instance1, "C# is the best language!");
Console.WriteLine($"Updated Property Value: {prop?.Name}");
  • GetProperties: 获取所有属性
PropertyInfo[] props = type1.GetProperties();
Console.WriteLine("All Properties:");
foreach (var p in props)
{
    Console.WriteLine($" - {p.Name} (Type: {p.PropertyType.Name})");
}
  • GetField: 获取指定字段
// 获取 PrivateField 字段, 并获取/设置值
FieldInfo? field = type1.GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine($"Private Field Value: {field?.GetValue(instance1)}");
field?.SetValue(instance1, 100);
Console.WriteLine($"Updated Private Field Value: {field?.GetValue(instance1)}");
  • GetFields: 获取所有字段
// 获取所有私有字段, 并列出
FieldInfo[] fields = type1.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("All Private Fields:");
foreach (var f in fields)
{
    Console.WriteLine($" - {f.Name} (Type: {f.FieldType.Name})");
}
  • ConstructorInfo: 获取指定构造函数
// 获取指定构造函数, 传递 Parameterized Constructor Value 参数并创建实例
ConstructorInfo? ctor = type1.GetConstructor(new [] { typeof(string) });
object? instance2 = ctor?.Invoke(new object[] { "Parameterized Constructor Value" });
Console.WriteLine($"instance2.PublicProperty: {prop?.GetValue(instance2, null)}");
  • MakeGenericMethod: 创建泛型方法
// 获取 Echo(int/string) 方法, 创建泛型方法并调用
MethodInfo? echoMethod = type1.GetMethod("Echo");
MethodInfo? echoInt = echoMethod?.MakeGenericMethod(typeof(int));
MethodInfo? echoString = echoMethod?.MakeGenericMethod(typeof(string));
var echoIntResult = echoInt?.Invoke(instance1, new object?[] { 123 });
var echoStringResult = echoString?.Invoke(instance1, new object?[] { "Hello Generic" });
Console.WriteLine($"Echo<int>: {echoIntResult}");
Console.WriteLine($"Echo<string>: {echoStringResult}");
  • GetCustomAttributes: 获取指定特性
// 列出所有 DemoAttribute 特性
object[] attributes = type1.GetCustomAttributes(typeof(DemoAttribute), false);
foreach (DemoAttribute attr in attributes)
{
    Console.WriteLine($"Custom Attribute - DemoAttribute Name: {attr.Name}");
}

性能优化

直接用 MethodInfo.Invoke 每次都要做很多检查。更快的办法是把 MethodInfo 转为委托(CreateDelegate)或使用表达式树生成委托(可处理非 public 或复杂签名)。

示例:把实例方法转为委托:

var printDelegate =
    (Action<Sample, string>)Delegate.CreateDelegate(typeof(Action<Sample, string>), null, print);
printDelegate((Sample)instance1, "via delegate");

更优做法:针对已知签名创建具体委托类型(见上面 Delegate.CreateDelegate 示例),或者使用 Expression 构建并编译一个委托(可以零拷贝传参,性能接近直接调用)。

完整例子

namespace Learn;
using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DemoAttribute : Attribute
{
    public string Name { get; set; }
    public DemoAttribute(string name) => Name = name;
}

[Demo("Sample Class")]
public class Sample
{
    public string PublicProperty { get; set; } = "I'm Public Property Value!";
    private int _privateField = 42;
    
    public Sample() {}
    
    public Sample(string publicProperty) => PublicProperty = publicProperty;

    public void Print(string content)
    {
        Console.WriteLine($"Print: {content}");
    }

    private string Secret()
    {
        return $"Private field value is {_privateField}";
    }
    
    public T Echo<T>(T input)
    {
        return input;
    }

    public static string StaticMethodToUpper(string s) => s.ToUpper();
}

class Program
{
    static void Main(string[] args)
    {
        // 1) Get Type
        Type type1 = typeof(Sample);
        object instance1 = Activator.CreateInstance(type1)!;
        
        // 2) List all public method
        Console.WriteLine("Public Methods: ");
        foreach (var m in type1.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
        {
            Console.WriteLine($"  - {m.Name} (IsStatic={m.IsStatic}, Return={m.ReturnType.Name})");
        }
        
        // 3) Call the public method
        MethodInfo? print = type1.GetMethod("Print");
        print?.Invoke(instance1, new object?[] { "Hello" });
        
        // 4) Access the property
        PropertyInfo? prop = type1.GetProperty("PublicProperty");
        Console.WriteLine($"{prop?.Name}: {prop?.GetValue(instance1, null)}");
        prop?.SetValue(instance1, "C# is the best language!");
        Console.WriteLine($"Updated Property Value: {prop?.Name}");
        
        PropertyInfo[] props = type1.GetProperties();
        Console.WriteLine("All Properties:");
        foreach (var p in props)
        {
            Console.WriteLine($" - {p.Name} (Type: {p.PropertyType.Name})");
        }
        
        print?.Invoke(instance1, new object?[] { prop?.GetValue(instance1, null) });
        
        // 5) Access the private field
        FieldInfo? field = type1.GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance);
        Console.WriteLine($"Private Field Value: {field?.GetValue(instance1)}");
        field?.SetValue(instance1, 100);
        Console.WriteLine($"Updated Private Field Value: {field?.GetValue(instance1)}");
        
        FieldInfo[] fields = type1.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
        Console.WriteLine("All Private Fields:");
        foreach (var f in fields)
        {
            Console.WriteLine($" - {f.Name} (Type: {f.FieldType.Name})");
        }

        print?.Invoke(instance1, new object?[] { Convert.ToString(field?.GetValue(instance1)) });
        
        // 6) Construct with parameterized constructor
        ConstructorInfo? ctor = type1.GetConstructor(new [] { typeof(string) });
        object? instance2 = ctor?.Invoke(new object[] { "Parameterized Constructor Value" });
        Console.WriteLine($"instance2.PublicProperty: {prop?.GetValue(instance2, null)}");
        
        // 7) Call the generic method
        MethodInfo? echoMethod = type1.GetMethod("Echo");
        MethodInfo? echoInt = echoMethod?.MakeGenericMethod(typeof(int));
        MethodInfo? echoString = echoMethod?.MakeGenericMethod(typeof(string));
        var echoIntResult = echoInt?.Invoke(instance1, new object?[] { 123 });
        var echoStringResult = echoString?.Invoke(instance1, new object?[] { "Hello Generic" });
        Console.WriteLine($"Echo<int>: {echoIntResult}");
        Console.WriteLine($"Echo<string>: {echoStringResult}");
        
        // 8) Call the static method
        MethodInfo? staticMethodToUpper = type1.GetMethod("StaticMethodToUpper", BindingFlags.Public | BindingFlags.Static);
        var staticResult = staticMethodToUpper?.Invoke(null, new object?[] {"Test Static Call"});
        Console.WriteLine($"Static Method Result: {staticResult}");
        
        // 9) Access custom attribute
        object[] attributes = type1.GetCustomAttributes(typeof(DemoAttribute), false);
        foreach (DemoAttribute attr in attributes)
        {
            Console.WriteLine($"Custom Attribute - DemoAttribute Name: {attr.Name}");
        }

        // 10) Load from dll, example here.
        // Assembly assembly = Assembly.LoadFrom("ExternalLibrary.dll");
        // Type? externalType = assembly.GetType("ExternalLibrary.ExternalClass");
        // object? externalInstance = Activator.CreateInstance(externalType);

        // 11) Performance enhancement
        var printDelegate =
            (Action<Sample, string>)Delegate.CreateDelegate(typeof(Action<Sample, string>), null, print);
        printDelegate((Sample)instance1, "via delegate");
        
    }
}