在C#的开发体系中,反射和元数据是支撑动态代码逻辑的核心能力,二者配合可以实现运行时的代码生成与无侵入式的代码扩展,大幅提升程序的灵活性和可扩展性。

反射与元数据的基础概念
反射是C#提供的一组API,允许程序在运行时检查、操作和调用程序集中的类型、方法、属性等成员,不需要在编译期确定具体的类型信息。元数据则是存储在程序集文件中的描述信息,包含程序集的版本、引用的程序集、定义的类型、类型的成员、成员的修饰符等所有静态描述内容,反射本质上就是读取这些元数据来实现动态操作。
反射的核心类
C#中反射相关的主要类都位于System.Reflection命名空间下,常用的包括:
Assembly:表示程序集,是获取元数据的入口,可以加载程序集并获取其中的类型信息Type:表示类型声明,包含类型的所有元数据信息,比如属性、方法、构造函数等MethodInfo:表示方法的信息,可用于动态调用方法PropertyInfo:表示属性的信息,可用于动态读写属性值
基于反射和元数据的代码生成实现
代码生成指的是在运行时根据现有类型的元数据,动态生成新的代码逻辑,比如自动生成数据访问层代码、自动生成DTO映射代码等。下面以动态生成简单的实体类属性赋值代码为例,展示实现过程。
示例:动态生成属性赋值方法
首先定义一个简单的实体类:
using System;
namespace ReflectionDemo
{
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
接下来通过反射读取User类的元数据,动态生成将字典数据赋值给User实例的代码:
using System;
using System.Collections.Generic;
using System.Reflection;
namespace ReflectionDemo
{
public class CodeGenerator
{
// 根据类型元数据生成属性赋值代码的方法
public static string GenerateAssignCode(Type type, string instanceName, string dictName)
{
// 获取类型的所有可写属性元数据
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
string code = $"{type.Name} {instanceName} = new {type.Name}();n";
foreach (PropertyInfo prop in properties)
{
// 判断属性是否可写
if (prop.CanWrite)
{
// 拼接赋值代码,这里假设字典的键和属性名一致
code += $"{instanceName}.{prop.Name} = ({prop.PropertyType.Name}){dictName}["{prop.Name}"];n";
}
}
return code;
}
public static void TestGenerate()
{
Type userType = typeof(User);
string generatedCode = GenerateAssignCode(userType, "user", "dataDict");
Console.WriteLine("生成的赋值代码:");
Console.WriteLine(generatedCode);
}
}
}
调用TestGenerate方法后,会输出如下动态生成的代码:
User user = new User(); user.Id = (Int32)dataDict["Id"]; user.Name = (String)dataDict["Name"]; user.Age = (Int32)dataDict["Age"];
基于反射和元数据的代码扩展实现
代码扩展指的是在不修改原有代码的前提下,通过反射读取元数据,为现有类型添加新的功能逻辑,常见的场景包括AOP切面编程、插件化扩展等。下面以无侵入式的日志扩展为例,展示实现过程。
示例:为方法添加自动日志扩展
首先定义一个标记需要添加日志的方法的特性元数据,特性本身是元数据的一种形式:
using System;
namespace ReflectionDemo
{
// 定义日志特性的元数据
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class LogAttribute : Attribute
{
public string OperationName { get; set; }
}
}
然后定义一个普通的业务类,其中部分方法标记了LogAttribute特性:
using System;
namespace ReflectionDemo
{
public class OrderService
{
[Log(OperationName = "创建订单")]
public void CreateOrder(string orderNo)
{
Console.WriteLine($"创建订单,订单号:{orderNo}");
}
public void QueryOrder(string orderNo)
{
Console.WriteLine($"查询订单,订单号:{orderNo}");
}
}
}
接下来通过反射读取OrderService的元数据,找到标记了LogAttribute的方法,在执行方法前后自动添加日志逻辑,实现无侵入扩展:
using System;
using System.Reflection;
namespace ReflectionDemo
{
public class LogExtension
{
public static void ExecuteWithLog(object serviceInstance, string methodName, params object[] parameters)
{
Type serviceType = serviceInstance.GetType();
// 获取方法元数据,这里仅处理无重载的简单方法
MethodInfo method = serviceType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
if (method == null)
{
throw new ArgumentException($"未找到方法{methodName}");
}
// 检查方法的元数据是否包含LogAttribute特性
LogAttribute logAttr = method.GetCustomAttribute<LogAttribute>();
if (logAttr != null)
{
Console.WriteLine($"【日志】开始执行操作:{logAttr.OperationName}");
}
// 调用原始方法
try
{
method.Invoke(serviceInstance, parameters);
}
catch (Exception ex)
{
Console.WriteLine($"【日志】操作执行失败:{ex.InnerException?.Message ?? ex.Message}");
throw;
}
if (logAttr != null)
{
Console.WriteLine($"【日志】操作执行完成:{logAttr.OperationName}");
}
}
}
}
测试扩展效果:
using System;
namespace ReflectionDemo
{
class Program
{
static void Main(string[] args)
{
OrderService orderService = new OrderService();
// 调用标记了Log特性的方法
LogExtension.ExecuteWithLog(orderService, "CreateOrder", "ORD20240501");
Console.WriteLine("---------------------");
// 调用未标记Log特性的方法
LogExtension.ExecuteWithLog(orderService, "QueryOrder", "ORD20240501");
}
}
}
执行后输出结果如下,可以看到标记了特性的方法自动添加了日志,未标记的方法则没有额外逻辑,实现了无修改原有业务代码的功能扩展:
【日志】开始执行操作:创建订单 创建订单,订单号:ORD20240501 【日志】操作执行完成:创建订单 --------------------- 查询订单,订单号:ORD20240501
注意事项
使用反射和元数据实现代码生成和扩展时,需要注意以下几点:
- 反射操作的性能比直接编译期调用低,频繁调用的场景建议缓存
Type、MethodInfo等元数据对象,避免重复获取 - 反射可以访问私有成员,使用时需要遵守封装原则,避免破坏原有类型的封装性
- 动态生成的代码如果需要执行,可以结合
System.Reflection.Emit命名空间下的类生成IL代码,或者使用Roslyn动态编译C#代码 - 特性作为元数据的一种,本身不会主动触发逻辑,需要配合反射读取后才能发挥作用