在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#强大的元编程工具,它们为框架开发、系统架构提供了极大的灵活性。虽然反射会带来一定的性能开销,但在合理的场景下使用,并配合适当的优化策略,可以构建出既灵活又高效的应用程序。理解这些技术的原理和适用场景,将使你能够更好地设计和实现复杂的系统架构。