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.Type 和 System.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}"); // False2. 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 组合 | 用途 |
|---|---|
| `Public | Instance` |
| `NonPublic | Instance` |
| `Public | Static` |
| `Public | Instance |
| `NonPublic | Instance |
三、动态操作
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); // 204. 动态调用泛型类型的方法
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-5x | 比 new 慢,但可接受 |
优化技巧
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) |
| 类型安全 | 编译时检查 | 运行时检查 |
| 可读性 | 好 | 差 |
| 灵活性 | 静态绑定 | 动态绑定 |
| 适用场景 | 常规开发 | 框架、工具、插件系统 |
注意事项
- 特性不改变代码行为——它只是元数据,需要其他代码(反射)来读取和响应
- 反射有性能开销——大量使用时缓存
Type、MethodInfo或使用委托优化 - 反射破坏封装——可以访问私有成员,仅在必要时使用
Activator.CreateInstance比new慢——但大多数场景差异可忽略- 反射在 AOT 环境下受限——需要提前生成代码或使用源生成器
- 异常处理很重要——反射操作可能抛出
TargetException、TargetInvocationException、MissingMethodException等 - 优先使用源生成器(Source Generator)——.NET 6+ 中,源生成器在编译时生成代码,兼具反射的灵活性和直接调用的性能


