C# 特性:Attribute 与反射获取
特性(Attribute)本质上是附加在代码元素(类、方法、属性等)上的元数据标签。它不改变程序本身的运行逻辑,但能提供额外的描述信息。通过反射(Reflection),你可以在程序运行期间动态读取这些标签的内容。按照以下步骤,完成从创建到读取的完整流程。
阶段一:定义特性类
特性本身就是一个普通的 C# 类,但必须遵守特定规则。
-
创建一个继承自
System.Attribute的公共类。在类名末尾添加Attribute后缀,这是 C# 的命名惯例,使用特性时该后缀会被自动省略。public class VersionInfoAttribute : System.Attribute { } -
配置作用范围。使用
AttributeUsage标记该特性,指定它可以贴在哪些元素上(如类、方法、字段),并设置是否允许重复标记。[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple = true)] public class VersionInfoAttribute : System.Attribute { } -
声明数据存储字段。特性需要载体来保存信息,添加只读属性或公共属性。构造函数负责接收必传参数。
public class VersionInfoAttribute : System.Attribute { public VersionInfoAttribute(string author) { Author = author; CreatedDate = System.DateTime.Now; } public string Author { get; } public string Version { get; set; } = "1.0.0"; public System.DateTime CreatedDate { get; } }
阶段二:将特性附加到目标代码
定义完成后,即可像贴标签一样将其绑定到类或方法上。
- 定位需要标记的代码位置。特性必须放置在目标元素正上方,用方括号
[]包裹。 - 传入参数。调用特性类的构造函数,必传参数直接填写,可选参数使用
属性名 = 值的格式。[VersionInfo("张三", Version = "2.5.0")] public class DataProcessor { [VersionInfo("李四", Version = "1.2.0")] public void Execute() { // 核心业务逻辑 } }
阶段三:使用反射机制读取特性
程序运行时,通过 System.Reflection 命名空间提取标签信息。
- 获取目标类型的元数据。调用
typeof()或实例的.GetType()方法获取Type对象。 - 提取特性对象。使用
GetCustomAttributes或GetCustomAttribute<T>泛型方法。推荐使用泛型版本,它直接返回强类型对象,避免手动类型转换。 - 遍历结果集合。如果特性允许重复,方法会返回数组或可枚举集合。
为清晰对比不同场景的提取方式,参考下表配置你的反射调用:
| 目标位置 | 核心反射方法 | 返回类型说明 | 调用示例片段 |
|---|---|---|---|
| 类级别 | Type.GetCustomAttribute<T>() |
返回单个特性实例 | typeof(DataProcessor).GetCustomAttribute<VersionInfoAttribute>() |
| 类级别(允许多个) | Type.GetCustomAttributes<T>() |
返回 IEnumerable<T> |
typeof(DataProcessor).GetCustomAttributes<VersionInfoAttribute>() |
| 方法级别 | MethodInfo.GetCustomAttribute<T>() |
返回单个特性实例 | method.GetCustomAttribute<VersionInfoAttribute>() |
| 属性/字段级别 | PropertyInfo.GetCustomAttribute<T>() |
返回单个特性实例 | prop.GetCustomAttribute<VersionInfoAttribute>() |
阶段四:完整读取与数据解析代码
将上述步骤整合为可执行的控制台程序。严格按照顺序编写代码即可直接运行。
- 导入反射命名空间。在文件头部引入
using System.Reflection;。 - 编写主读取逻辑。获取目标类型后,使用
foreach循环处理可能存在的多个特性实例,并安全读取属性值。using System; using System.Reflection;
class Program
{
static void Main()
{
// 1. 获取目标类型的 Type 对象
Type targetType = typeof(DataProcessor);
// 2. 提取该类型上的所有特性
var attributes = targetType.GetCustomAttributes<VersionInfoAttribute>();
// 3. 遍历并读取特性数据
foreach (var attr in attributes)
{
Console.WriteLine($"作者: {attr.Author}");
Console.WriteLine($"版本: {attr.Version}");
Console.WriteLine($"创建时间: {attr.CreatedDate:yyyy-MM-dd}");
}
// 4. 演示方法级别的特性获取
MethodInfo method = targetType.GetMethod("Execute");
if (method != null)
{
var methodAttr = method.GetCustomAttribute<VersionInfoAttribute>();
if (methodAttr != null)
{
Console.WriteLine($"方法开发者: {methodAttr.Author}");
}
}
}
}
3. **处理**潜在的空引用异常。反射获取特性时,若目标未打标签,返回结果为 `null`(单实例方法)或空集合(多实例方法)。**添加**空值检查逻辑,避免程序崩溃。上述代码中已使用 `if (methodAttr != null)` 进行防护。
---
## 关键避坑指南
特性与反射的组合在实际开发中极易因细节疏忽导致读取失败。严格执行以下检查项。
1. **核对** `AttributeUsage` 的 `AttributeTargets` 枚举值。如果只允许贴在类上,却尝试贴在接口上,编译器会直接报错。
2. **确认**泛型参数类型。调用 `GetCustomAttribute<T>()` 时,尖括号 `<>` 内的类型**必须**与你定义的特性类名完全一致(不包含 `Attribute` 后缀亦可,但建议带全称以避免歧义)。
3. **注意**编译优化影响。在 `Release` 模式下启用“优化代码”时,未被引用的方法或类型可能被裁剪。若反射读取失效,**检查**项目发布配置中的“裁剪未引用程序集”或“优化”开关。
4. **区分**实例方法与静态方法的反射获取。静态方法同样通过 `GetMethod` 获取,但若方法名重载,需额外传入 `BindingFlags.Static | BindingFlags.Public` 参数数组以确保精准定位。
完成上述配置后,特性即可作为代码的标准化配置源,替代部分硬编码参数或外部配置文件,实现配置与逻辑的紧密绑定。
暂无评论,快来抢沙发吧!