Skip to content

02-反射和特性

反射(Reflection)和特性(Attribute)是 C# 中强大的元编程工具。反射允许在运行时检查类型信息并动态调用成员,特性用于向代码添加声明性元数据。


一、特性(Attribute)

特性是向代码元素(类、方法、属性等)添加描述性信息的声明性标记。特性使用方括号 [] 语法。

1. 内置特性

csharp
// [Obsolete]:标记已过时的成员,使用时会产生编译警告或错误
[Obsolete("请使用 NewMethod 代替")]
public static void OldMethod()
{
    Console.WriteLine("旧方法");
}

[Obsolete("此方法已移除", true)]  // true = 使用时会编译错误
public static void RemovedMethod() { }

// [Serializable]:标记类可序列化
[Serializable]
public class Person
{
    public string Name { get; set; }
}

// [Conditional]:条件编译——仅在定义了指定符号时才编译
#define DEBUG
using System.Diagnostics;

public class Logger
{
    [Conditional("DEBUG")]
    public static void Log(string message)
    {
        Console.WriteLine($"DEBUG: {message}");
    }

    [Conditional("RELEASE")]
    public static void LogRelease(string message)
    {
        Console.WriteLine($"RELEASE: {message}");
    }
}

2. 常用内置特性速查

特性目标用途
[Obsolete]所有成员标记废弃,提供迁移提示
[Serializable]类、结构标记可序列化
[Conditional]方法条件编译
[CallerMemberName]可选参数自动填入调用者方法名
[CallerFilePath]可选参数自动填入调用者文件路径
[CallerLineNumber]可选参数自动填入调用者行号
[DllImport]方法调用非托管 DLL 函数
[Flags]枚举标记枚举为位标志
[DebuggerDisplay]类、属性自定义调试器显示内容

3. Caller Info 特性示例

csharp
using System.Runtime.CompilerServices;

public static class DebugHelper
{
    public static void Log(string message,
        [CallerMemberName] string member = "",
        [CallerFilePath] string file = "",
        [CallerLineNumber] int line = 0)
    {
        string fileName = Path.GetFileName(file);
        Console.WriteLine($"[{member}] {fileName}:{line} - {message}");
    }
}

public class OrderService
{
    public void CreateOrder()
    {
        DebugHelper.Log("开始创建订单");  // 自动填入调用者信息
        // 输出:[CreateOrder] OrderService.cs:12 - 开始创建订单
    }
}

4. 自定义特性

csharp
// 定义特性类——必须继承 System.Attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property,
    AllowMultiple = true,    // 是否允许重复使用
    Inherited = false)]      // 是否被继承
public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public string Version { get; set; }  // 命名参数(可选)

    // 构造函数参数 = 必需的位置参数
    public AuthorAttribute(string name)
    {
        Name = name;
    }
}

// 使用自定义特性
[Author("张三", Version = "1.0")]
[Author("李四", Version = "2.0")]  // AllowMultiple = true 允许重复
public class SampleClass
{
    [Author("王五")]
    public void MyMethod() { }

    [Author("赵六")]
    public string MyProperty { get; set; }
}

AttributeUsage 参数详解

参数说明示例值
AttributeTargets特性可应用的目标Class, Method, Property
AllowMultiple是否允许重复使用(默认 false)true / false
Inherited派生类是否继承此特性(默认 true)true / false

二、反射基础

反射通过 System.TypeSystem.Reflection 命名空间中的类来工作。

1. 获取 Type 的三种方式

csharp
using System.Reflection;

// 方式一:typeof 运算符(最常用,编译时确定)
Type type1 = typeof(string);

// 方式二:GetType() 方法(运行时确定)
string s = "hello";
Type type2 = s.GetType();

// 方式三:Type.GetType() 静态方法(通过字符串名称)
Type type3 = Type.GetType("System.String");
Type type4 = Type.GetType("System.Collections.Generic.List`1[[System.Int32]]"); // List<int>

Console.WriteLine($"名称:{type1.Name}");                    // String
Console.WriteLine($"完整名称:{type1.FullName}");            // System.String
Console.WriteLine($"命名空间:{type1.Namespace}");           // System
Console.WriteLine($"是否为类:{type1.IsClass}");             // True
Console.WriteLine($"是否为值类型:{type1.IsValueType}");     // False
Console.WriteLine($"是否为抽象:{type1.IsAbstract}");        // False
Console.WriteLine($"是否为泛型:{type1.IsGenericType}");     // False

2. Type 成员信息获取

csharp
public class MyClass
{
    public int Id { get; set; }
    public string Name { get; set; } = "默认";

    public void PrintInfo()
    {
        Console.WriteLine($"Id={Id}, Name={Name}");
    }

    public void PrintInfo(string prefix)
    {
        Console.WriteLine($"{prefix}: Id={Id}, Name={Name}");
    }

    private void SecretMethod() { }

    public static void StaticMethod() { }
}

Type type = typeof(MyClass);

// 获取所有公有属性
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
    Console.WriteLine($"属性:{prop.Name} ({prop.PropertyType.Name})");
    Console.WriteLine($"  可读:{prop.CanRead},可写:{prop.CanWrite}");
}

// 获取方法(指定 BindingFlags)
MethodInfo[] methods = type.GetMethods(
    BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (var method in methods)
{
    Console.WriteLine($"方法:{method.Name}");
    Console.WriteLine($"  返回类型:{method.ReturnType.Name}");

    ParameterInfo[] parameters = method.GetParameters();
    foreach (var param in parameters)
        Console.WriteLine($"  参数:{param.ParameterType.Name} {param.Name}");
}

// 获取字段(包括私有)
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
    Console.WriteLine($"字段:{field.FieldType.Name} {field.Name}");
}

BindingFlags 常用组合

BindingFlags 组合用途
`PublicInstance`
`NonPublicInstance`
`PublicStatic`
`PublicInstance
`NonPublicInstance

三、动态操作

1. 动态创建对象

csharp
public class Calculator
{
    public string Name { get; set; } = "计算器";

    public Calculator() { }

    public Calculator(string name)
    {
        Name = name;
    }

    public int Add(int a, int b) => a + b;
}

Type calcType = typeof(Calculator);

// 方式一:无参构造函数
object? calc1 = Activator.CreateInstance(calcType);

// 方式二:带参构造函数
object? calc2 = Activator.CreateInstance(calcType, new object[] { "高级计算器" });

// 方式三:泛型版本
Calculator? calc3 = Activator.CreateInstance<Calculator>();

// 方式四:创建泛型类型的实例
Type dictType = typeof(Dictionary<,>);
Type closedType = dictType.MakeGenericType(typeof(string), typeof(int));
IDictionary<string, int> dict = (IDictionary<string, int>)Activator.CreateInstance(closedType);
dict["张三"] = 85;

2. 动态调用方法

csharp
Type calcType = typeof(Calculator);
object? calc = Activator.CreateInstance(calcType);

// 调用无参方法
MethodInfo addMethod = calcType.GetMethod("Add");
object? result = addMethod?.Invoke(calc, new object[] { 3, 5 });
Console.WriteLine($"结果:{result}");  // 8

// 调用带参方法
MethodInfo greetMethod = calcType.GetMethod("Greet", new[] { typeof(string) });
// ⚠️ 方法重载时需要指定参数类型来区分

// 设置和获取属性
PropertyInfo nameProp = calcType.GetProperty("Name");
nameProp?.SetValue(calc, "我的计算器");
string? name = nameProp?.GetValue(calc) as string;

3. 动态调用泛型方法

csharp
public class Utility
{
    public T Max<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b) > 0 ? a : b;
    }
}

Type utilType = typeof(Utility);
object util = Activator.CreateInstance(utilType);

// 获取泛型方法定义
MethodInfo maxMethod = utilType.GetMethod("Max");
// 通过 MakeGenericMethod 指定类型参数
MethodInfo maxInt = maxMethod.MakeGenericMethod(typeof(int));
object result = maxInt.Invoke(util, new object[] { 10, 20 });
Console.WriteLine(result);  // 20

4. 动态调用泛型类型的方法

csharp
// 调用 List<T>.Add 方法
Type listType = typeof(List<>);
Type stringListType = listType.MakeGenericType(typeof(string));
object list = Activator.CreateInstance(stringListType);

MethodInfo addMethod = stringListType.GetMethod("Add");
addMethod?.Invoke(list, new object[] { "Hello" });
addMethod?.Invoke(list, new object[] { "World" });

// 获取 Count 属性
PropertyInfo countProp = stringListType.GetProperty("Count");
int count = (int)countProp.GetValue(list);
Console.WriteLine(count);  // 2

四、反射读取特性

csharp
// 定义特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DescriptionAttribute : Attribute
{
    public string Text { get; }
    public int Priority { get; set; }

    public DescriptionAttribute(string text)
    {
        Text = text;
    }
}

// 使用特性
[Description("这个类用于处理用户数据", Priority = 1)]
public class UserProcessor
{
    [Description("获取用户信息的方法")]
    public void GetUser() { }

    [Description("保存用户信息的方法", Priority = 2)]
    public void SaveUser() { }
}

// 反射读取特性
Type userType = typeof(UserProcessor);

// 读取类上的特性
DescriptionAttribute? classDesc = userType.GetCustomAttribute<DescriptionAttribute>();
if (classDesc != null)
{
    Console.WriteLine($"类描述:{classDesc.Text},优先级:{classDesc.Priority}");
}

// 读取方法上的特性
MethodInfo[] methods = userType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (var method in methods)
{
    DescriptionAttribute? methodDesc = method.GetCustomAttribute<DescriptionAttribute>();
    if (methodDesc != null)
    {
        Console.WriteLine($"方法 {method.Name}:{methodDesc.Text}");
    }
}

// 读取所有特性(包括继承的)
object[] allAttributes = userType.GetCustomAttributes(true);
foreach (var attr in allAttributes)
{
    Console.WriteLine($"特性类型:{attr.GetType().Name}");
}

五、反射访问私有成员

csharp
public class SecretHolder
{
    private string secret = "这是个秘密";
    private int hiddenNumber = 42;
    private static string globalSecret = "全局秘密";

    private void PrivateMethod()
    {
        Console.WriteLine($"私有方法被调用,secret = {secret}");
    }
}

SecretHolder holder = new SecretHolder();
Type holderType = typeof(SecretHolder);

// 读取私有实例字段
FieldInfo secretField = holderType.GetField("secret",
    BindingFlags.NonPublic | BindingFlags.Instance);
string secretValue = (string)secretField?.GetValue(holder);
Console.WriteLine($"秘密:{secretValue}");  // 秘密:这是个秘密

// 修改私有字段
FieldInfo numberField = holderType.GetField("hiddenNumber",
    BindingFlags.NonPublic | BindingFlags.Instance);
numberField?.SetValue(holder, 100);
Console.WriteLine($"修改后的值:{numberField?.GetValue(holder)}");  // 100

// 读取私有静态字段
FieldInfo globalField = holderType.GetField("globalSecret",
    BindingFlags.NonPublic | BindingFlags.Static);
Console.WriteLine($"全局秘密:{globalField?.GetValue(null)}");  // 全局秘密

// 调用私有方法
MethodInfo privateMethod = holderType.GetMethod("PrivateMethod",
    BindingFlags.NonPublic | BindingFlags.Instance);
privateMethod?.Invoke(holder, null);  // 私有方法被调用,secret = 这是个秘密

注意: 私有成员反射应谨慎使用,破坏了封装性,仅在测试、序列化、框架开发等场景使用。


六、程序集反射

csharp
// 获取当前程序集
Assembly currentAssembly = Assembly.GetExecutingAssembly();
Console.WriteLine($"程序集名称:{currentAssembly.FullName}");
Console.WriteLine($"入口点:{currentAssembly.EntryPoint?.Name}");
Console.WriteLine($"位置:{currentAssembly.Location}");

// 获取调用者的程序集
Assembly callingAssembly = Assembly.GetCallingAssembly();

// 获取入口程序集
Assembly entryAssembly = Assembly.GetEntryAssembly();

// 获取所有公开类型
Type[] types = currentAssembly.GetExportedTypes();
foreach (Type t in types)
{
    Console.WriteLine($"公开类型:{t.FullName}");
}

// 加载外部程序集
// Assembly external = Assembly.LoadFrom(@"C:\Plugins\MyPlugin.dll");
// Assembly external = Assembly.Load("System.Text.Json");

// 从程序集创建实例
// Type? pluginType = external.GetType("MyPlugin.PluginClass");
// object? plugin = Activator.CreateInstance(pluginType);

七、反射的性能问题与优化

性能对比

调用方式相对耗时说明
直接调用1x最快
委托调用~2-3x比直接调用略慢
MethodInfo.Invoke~10-50x反射调用,有参数检查等开销
Activator.CreateInstance~2-5xnew 慢,但可接受

优化技巧

csharp
public class PerformanceDemo
{
    public int Add(int a, int b) => a + b;
}

// 方式一:直接调用(最快)
var demo = new PerformanceDemo();
int r1 = demo.Add(3, 5);  // 1x

// 方式二:反射 Invoke(最慢)
Type type = typeof(PerformanceDemo);
object obj = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod("Add");
int r2 = (int)method.Invoke(obj, new object[] { 3, 5 });  // 10-50x

// 方式三:委托优化(推荐大量调用时)
Func<PerformanceDemo, int, int, int> addDelegate =
    (Func<PerformanceDemo, int, int, int>)Delegate.CreateDelegate(
        typeof(Func<PerformanceDemo, int, int, int>), null, method);
int r3 = addDelegate(demo, 3, 5);  // 2-3x

// 方式四:缓存 MethodInfo(避免重复 GetMethod)
// 将 MethodInfo 缓存到静态字段中重复使用

// 方式五:使用 Expression Tree 编译
using System.Linq.Expressions;

Expression<Func<PerformanceDemo, int, int, int>> expr =
    (p, a, b) => p.Add(a, b);
Func<PerformanceDemo, int, int, int> compiled = expr.Compile();
int r4 = compiled(demo, 3, 5);  // ~1-2x(接近直接调用)

八、综合案例

案例一:依赖注入容器

csharp
public class SimpleContainer
{
    private Dictionary<Type, Type> mappings = new Dictionary<Type, Type>();
    private Dictionary<Type, object> singletons = new Dictionary<Type, object>();

    public void Register<TInterface, TImplementation>()
        where TImplementation : TInterface
    {
        mappings[typeof(TInterface)] = typeof(TImplementation);
    }

    public void RegisterSingleton<TInterface, TImplementation>()
        where TImplementation : TInterface
    {
        mappings[typeof(TInterface)] = typeof(TImplementation);
        singletons[typeof(TInterface)] = null; // 延迟创建
    }

    public T Resolve<T>()
    {
        return (T)Resolve(typeof(T));
    }

    private object Resolve(Type type)
    {
        // 检查单例
        if (singletons.ContainsKey(type))
        {
            if (singletons[type] == null)
                singletons[type] = CreateInstance(mappings[type]);
            return singletons[type];
        }

        if (!mappings.ContainsKey(type))
            throw new Exception($"类型 {type.Name} 未注册");

        return CreateInstance(mappings[type]);
    }

    private object CreateInstance(Type type)
    {
        ConstructorInfo ctor = type.GetConstructors()
            .OrderByDescending(c => c.GetParameters().Length)
            .First();  // 选择参数最多的构造函数

        ParameterInfo[] parameters = ctor.GetParameters();
        object[] args = parameters
            .Select(p => Resolve(p.ParameterType))  // 递归解析依赖
            .ToArray();

        return Activator.CreateInstance(type, args);
    }
}

// 使用
public interface ILogger { void Log(string msg); }
public class ConsoleLogger : ILogger
{
    public void Log(string msg) => Console.WriteLine(msg);
}

public interface IUserService { void CreateUser(string name); }
public class UserService : IUserService
{
    private ILogger logger;
    public UserService(ILogger logger) => this.logger = logger;

    public void CreateUser(string name)
    {
        logger.Log($"用户 {name} 创建成功");
    }
}

var container = new SimpleContainer();
container.Register<ILogger, ConsoleLogger>();
container.Register<IUserService, UserService>();

var userService = container.Resolve<IUserService>();
userService.CreateUser("张三");  // 输出:用户 张三 创建成功

案例二:ORM 对象映射器

csharp
// 模拟 ORM 将 DataTable 映射为对象列表
public static class OrmMapper
{
    public static List<T> MapToObjects<T>(System.Data.DataTable table) where T : new()
    {
        var result = new List<T>();
        var properties = typeof(T).GetProperties();

        foreach (System.Data.DataRow row in table.Rows)
        {
            T obj = new T();
            foreach (var prop in properties)
            {
                if (table.Columns.Contains(prop.Name) && row[prop.Name] != DBNull.Value)
                {
                    // 处理类型转换
                    object value = Convert.ChangeType(row[prop.Name], prop.PropertyType);
                    prop.SetValue(obj, value);
                }
            }
            result.Add(obj);
        }

        return result;
    }
}

// 使用
public class UserModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime CreateTime { get; set; }
}

// var users = OrmMapper.MapToObjects<UserModel>(dataTable);

案例三:配置文件自动绑定

csharp
// 定义配置映射特性
[AttributeUsage(AttributeTargets.Property)]
public class ConfigKeyAttribute : Attribute
{
    public string Key { get; }
    public ConfigKeyAttribute(string key) => Key = key;
}

// 配置绑定器
public static class ConfigBinder
{
    public static T Bind<T>(Dictionary<string, string> config) where T : new()
    {
        T obj = new T();
        var properties = typeof(T).GetProperties();

        foreach (var prop in properties)
        {
            string key = prop.Name;

            // 优先使用 ConfigKey 特性中指定的键名
            var attr = prop.GetCustomAttribute<ConfigKeyAttribute>();
            if (attr != null)
                key = attr.Key;

            if (config.TryGetValue(key, out string value))
            {
                object converted = Convert.ChangeType(value, prop.PropertyType);
                prop.SetValue(obj, converted);
            }
        }

        return obj;
    }
}

// 使用
public class AppConfig
{
    [ConfigKey("db_connection")]
    public string DbConnection { get; set; }

    [ConfigKey("max_retries")]
    public int MaxRetries { get; set; }

    [ConfigKey("enable_logging")]
    public bool EnableLogging { get; set; }

    [ConfigKey("timeout_seconds")]
    public double TimeoutSeconds { get; set; }
}

var configDict = new Dictionary<string, string>
{
    ["db_connection"] = "Server=localhost;Database=test",
    ["max_retries"] = "3",
    ["enable_logging"] = "true",
    ["timeout_seconds"] = "30.5"
};

var config = ConfigBinder.Bind<AppConfig>(configDict);
Console.WriteLine(config.DbConnection);    // Server=localhost;Database=test
Console.WriteLine(config.MaxRetries);      // 3
Console.WriteLine(config.EnableLogging);   // True
Console.WriteLine(config.TimeoutSeconds);  // 30.5

案例四:插件加载系统

csharp
public interface IPlugin
{
    string Name { get; }
    void Execute();
    void Initialize();
}

public static class PluginLoader
{
    public static List<IPlugin> LoadPlugins(string pluginFolder)
    {
        List<IPlugin> plugins = new List<IPlugin>();

        if (!Directory.Exists(pluginFolder))
            return plugins;

        foreach (string dll in Directory.GetFiles(pluginFolder, "*.dll"))
        {
            try
            {
                Assembly assembly = Assembly.LoadFrom(dll);
                foreach (Type type in assembly.GetTypes())
                {
                    if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
                    {
                        IPlugin? plugin = Activator.CreateInstance(type) as IPlugin;
                        if (plugin != null)
                        {
                            plugin.Initialize();
                            plugins.Add(plugin);
                            Console.WriteLine($"加载插件:{plugin.Name}");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"加载 {dll} 失败:{ex.Message}");
            }
        }

        return plugins;
    }
}

核心知识点总结

特性速查

要点说明
定义继承 Attribute
使用语法[特性名(参数, 命名参数=值)]
AttributeUsage指定特性的应用目标、是否重复、是否继承
位置参数构造函数的参数(必需)
命名参数公有属性或字段(可选)
读取通过反射的 GetCustomAttribute 方法

反射核心类

说明示例
Type类型信息typeof(string)
PropertyInfo属性信息type.GetProperties()
MethodInfo方法信息type.GetMethods()
FieldInfo字段信息type.GetFields()
ConstructorInfo构造函数信息type.GetConstructors()
ParameterInfo参数信息method.GetParameters()
Assembly程序集信息Assembly.GetExecutingAssembly()

反射 vs 正常调用

对比正常调用反射调用
性能慢(10-50x)
类型安全编译时检查运行时检查
可读性
灵活性静态绑定动态绑定
适用场景常规开发框架、工具、插件系统

注意事项

  1. 特性不改变代码行为——它只是元数据,需要其他代码(反射)来读取和响应
  2. 反射有性能开销——大量使用时缓存 TypeMethodInfo 或使用委托优化
  3. 反射破坏封装——可以访问私有成员,仅在必要时使用
  4. Activator.CreateInstancenew——但大多数场景差异可忽略
  5. 反射在 AOT 环境下受限——需要提前生成代码或使用源生成器
  6. 异常处理很重要——反射操作可能抛出 TargetExceptionTargetInvocationExceptionMissingMethodException
  7. 优先使用源生成器(Source Generator)——.NET 6+ 中,源生成器在编译时生成代码,兼具反射的灵活性和直接调用的性能

Released under the MIT License.