在C#开发中,我们通常编写静态类型的代码——编译器在编译时就知道所有类型信息。然而,.NET框架提供了一套强大的机制,允许我们在运行时检查、发现和使用类型信息,这就是反射(Reflection)。而与反射密切相关的另一项技术是特性(Attribute),它为我们提供了一种向代码添加元数据的声明式方法。本文将全面探讨这两项技术,从基础概念到高级应用场景。
第一部分:反射基础
1.1 什么是反射?
反射是.NET框架的核心功能之一,它允许程序在运行时:
-
检查类型信息(类、接口、结构等)
-
动态创建对象实例
-
调用方法和访问属性/字段
-
修改程序行为而不需要重新编译
反射的核心是System.Type
类,它代表了类型声明。每个加载到应用程序域中的类型都有一个相关的Type对象。
1.2 获取Type对象的三种方式
// 1. 使用typeof运算符
Type t1 = typeof(string);// 2. 使用对象的GetType()方法
string s = "hello";
Type t2 = s.GetType();// 3. 使用Type.GetType()静态方法
Type t3 = Type.GetType("System.String");
1.3 反射的基本操作
检查类型信息
Type type = typeof(DateTime);Console.WriteLine($"类型名: {type.Name}");
Console.WriteLine($"全名: {type.FullName}");
Console.WriteLine($"命名空间: {type.Namespace}");
Console.WriteLine($"是类吗? {type.IsClass}");
Console.WriteLine($"是值类型吗? {type.IsValueType}");
Console.WriteLine($"基类型: {type.BaseType}");
检查成员信息
// 获取所有公共方法
MethodInfo[] methods = type.GetMethods();
foreach (MethodInfo method in methods)
{Console.WriteLine($"方法: {method.Name}");Console.WriteLine($" 返回类型: {method.ReturnType}");ParameterInfo[] parameters = method.GetParameters();foreach (ParameterInfo param in parameters){Console.WriteLine($" 参数: {param.Name} ({param.ParameterType})");}
}// 获取属性
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo prop in properties)
{Console.WriteLine($"属性: {prop.Name} ({prop.PropertyType})");
}
动态创建实例
// 使用无参构造函数
object stringInstance = Activator.CreateInstance(typeof(string));// 使用带参构造函数
char[] chars = {'H','e','l','l','o'};
object strWithChars = Activator.CreateInstance(typeof(string), chars);
Console.WriteLine(strWithChars); // 输出 "Hello"
动态调用方法
Type mathType = typeof(Math);
MethodInfo maxMethod = mathType.GetMethod("Max", new Type[] { typeof(int), typeof(int) });int result = (int)maxMethod.Invoke(null, new object[] { 5, 10 });
Console.WriteLine($"Max(5, 10) = {result}"); // 输出 10
第二部分:特性详解
2.1 什么是特性?
特性是向程序集、类型、成员等代码元素添加声明性信息的机制。它们不会直接影响代码的执行,但可以通过反射在运行时被读取和使用。
2.2 内置常用特性
.NET框架提供了许多有用的内置特性:
[Serializable] // 标记类可序列化
public class Person
{[Obsolete("该方法已过时,请使用NewMethod代替", true)] // 标记方法过时public void OldMethod() { }public void NewMethod() { }[NonSerialized] // 标记字段不序列化private string secret;[DllImport("user32.dll")] // 声明外部DLL方法public static extern int MessageBox(int hWnd, string text, string caption, int type);
}
2.3 自定义特性
创建自定义特性需要继承自System.Attribute
类:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class AuthorAttribute : Attribute
{public string Name { get; }public double Version { get; set; }public AuthorAttribute(string name){Name = name;}
}
-
AttributeUsage
特性用于指定自定义特性的使用规则:-
AttributeTargets
- 指定特性可以应用的目标 -
AllowMultiple
- 是否允许多次应用于同一目标 -
Inherited
- 是否可被派生类继承
-
2.4 使用自定义特性
[Author("John Doe", Version = 1.0)]
[Author("Jane Smith", Version = 1.1)]
public class Document
{[Author("John Doe")]public void Save() { }
}
2.5 通过反射读取特性
Type docType = typeof(Document);// 获取类上的特性
object[] attrs = docType.GetCustomAttributes(typeof(AuthorAttribute), false);
foreach (AuthorAttribute attr in attrs)
{Console.WriteLine($"作者: {attr.Name}, 版本: {attr.Version}");
}// 获取方法上的特性
MethodInfo saveMethod = docType.GetMethod("Save");
attrs = saveMethod.GetCustomAttributes(typeof(AuthorAttribute), false);
foreach (AuthorAttribute attr in attrs)
{Console.WriteLine($"方法作者: {attr.Name}");
}
第三部分:高级应用场景
3.1 插件系统实现
反射是实现插件架构的理想选择:
public interface IPlugin
{string Name { get; }string Description { get; }void Execute();
}public class PluginLoader
{public IEnumerable<IPlugin> LoadPlugins(string pluginsDirectory){if (!Directory.Exists(pluginsDirectory))throw new DirectoryNotFoundException(pluginsDirectory);var plugins = new List<IPlugin>();foreach (string dllPath in Directory.GetFiles(pluginsDirectory, "*.dll")){try{Assembly assembly = Assembly.LoadFrom(dllPath);foreach (Type type in assembly.GetTypes()){if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface){IPlugin plugin = (IPlugin)Activator.CreateInstance(type);plugins.Add(plugin);}}}catch (Exception ex){// 处理加载错误Console.WriteLine($"加载插件 {dllPath} 失败: {ex.Message}");}}return plugins;}
}
3.2 ORM框架中的特性映射
特性常用于ORM框架中实现对象-关系映射:
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{public string Name { get; }public TableAttribute(string name){Name = name;}
}[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{public string Name { get; }public bool IsPrimaryKey { get; set; }public bool IsNullable { get; set; } = true;public ColumnAttribute(string name){Name = name;}
}[Table("Users")]
public class User
{[Column("user_id", IsPrimaryKey = true)]public int Id { get; set; }[Column("user_name")]public string Name { get; set; }[Column("email")]public string Email { get; set; }[Column("created_at", IsNullable = false)]public DateTime CreatedAt { get; set; }
}public class SqlGenerator
{public string GenerateCreateTableSql(Type entityType){var tableAttr = entityType.GetCustomAttribute<TableAttribute>();string tableName = tableAttr?.Name ?? entityType.Name;var columns = new List<string>();var primaryKeys = new List<string>();foreach (var prop in entityType.GetProperties()){var columnAttr = prop.GetCustomAttribute<ColumnAttribute>();if (columnAttr == null) continue;string columnName = columnAttr.Name;string columnType = GetSqlType(prop.PropertyType);string nullable = columnAttr.IsNullable ? "NULL" : "NOT NULL";columns.Add($"{columnName} {columnType} {nullable}");if (columnAttr.IsPrimaryKey)primaryKeys.Add(columnName);}string sql = $"CREATE TABLE {tableName} (\n {string.Join(",\n ", columns)}";if (primaryKeys.Count > 0)sql += $",\n PRIMARY KEY ({string.Join(", ", primaryKeys)})";sql += "\n);";return sql;}private string GetSqlType(Type type){if (type == typeof(int)) return "INT";if (type == typeof(string)) return "VARCHAR(255)";if (type == typeof(DateTime)) return "DATETIME";if (type == typeof(bool)) return "BIT";// 添加更多类型映射...return "VARCHAR(255)";}
}
3.3 依赖注入容器
反射是实现依赖注入容器的核心技术:
public class DIContainer
{private readonly Dictionary<Type, Type> _mappings = new Dictionary<Type, Type>();public void Register<TInterface, TImplementation>() where TImplementation : TInterface{_mappings[typeof(TInterface)] = typeof(TImplementation);}public T Resolve<T>(){return (T)Resolve(typeof(T));}private object Resolve(Type type){Type implType;if (_mappings.TryGetValue(type, out implType)){// 获取第一个构造函数ConstructorInfo ctor = implType.GetConstructors()[0];// 获取构造函数参数ParameterInfo[] paramsInfo = ctor.GetParameters();// 解析所有参数object[] parameters = paramsInfo.Select(p => Resolve(p.ParameterType)).ToArray();// 创建实例return ctor.Invoke(parameters);}throw new InvalidOperationException($"未注册类型 {type.FullName}");}
}// 使用示例
var container = new DIContainer();
container.Register<ILogger, FileLogger>();
container.Register<IDatabase, SqlDatabase>();
container.Register<App, App>();App app = container.Resolve<App>();
第四部分:性能优化与最佳实践
4.1 反射的性能问题
反射操作比直接代码调用要慢得多,主要原因包括:
-
运行时类型检查
-
方法调用的间接性
-
缺少编译时优化
4.2 优化反射性能的策略
缓存反射结果
public class ReflectionCache
{private static readonly Dictionary<Type, PropertyInfo[]> _propertyCache = new Dictionary<Type, PropertyInfo[]>();public static PropertyInfo[] GetProperties(Type type){if (!_propertyCache.TryGetValue(type, out var properties)){properties = type.GetProperties();_propertyCache[type] = properties;}return properties;}
}
使用Delegate.CreateDelegate
MethodInfo method = typeof(string).GetMethod("Substring", new Type[] { typeof(int) });
var substringDelegate = (Func<string, int, string>)Delegate.CreateDelegate(typeof(Func<string, int, string>), method);// 现在可以高效调用
string result = substringDelegate("Hello World", 6);
使用表达式树
MethodInfo method = typeof(string).GetMethod("Substring", new Type[] { typeof(int) });var param = Expression.Parameter(typeof(string), "s");
var arg = Expression.Parameter(typeof(int), "start");
var call = Expression.Call(param, method, arg);
var lambda = Expression.Lambda<Func<string, int, string>>(call, param, arg);Func<string, int, string> compiled = lambda.Compile();
string result = compiled("Hello World", 6);
4.3 最佳实践
-
避免过度使用反射:只在必要时使用反射,如插件系统、序列化等场景
-
封装反射代码:将反射代码封装在专门的类中,与业务逻辑分离
-
安全考虑:反射可以绕过访问修饰符限制,需注意安全性
-
异常处理:反射操作可能抛出多种异常,需妥善处理
-
文档记录:使用反射的代码应充分注释,说明其用途和限制
结语
反射和特性是C#强大的元编程工具,它们为框架开发、系统架构提供了极大的灵活性。虽然反射会带来一定的性能开销,但在合理的场景下使用,并配合适当的优化策略,可以构建出既灵活又高效的应用程序。理解这些技术的原理和适用场景,将使你能够更好地设计和实现复杂的系统架构。