← 全部文章 C# 语言

C# 自定义特性与反射

2022 年 4 月 28 日 · 1 分钟读完

目录
  1. 定义一个特性
  2. 贴上去
  3. 用反射读出来
  4. 延伸:实际能拿来做什么

**特性(Attribute)**是贴在代码上的标签,本身不做任何事;**反射(Reflection)**是在运行时读取类型信息的能力。两者合在一起,就能「看标签办事」。概念介绍可以看菜鸟教程的特性和反射。

定义一个特性

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field |
                AttributeTargets.Method | AttributeTargets.Class,
                AllowMultiple = false, Inherited = true)]
public class CustomAttribute : Attribute // 必须继承 Attribute,名字按惯例以 Attribute 结尾
{
    public string Name { get; }

    public CustomAttribute(string name)
    {
        Name = name;
    }
}

AttributeUsage 规定这个特性能贴在哪里(AttributeTargets)、能不能贴多个(AllowMultiple)、子类能不能继承到(Inherited)。

贴上去

使用时可以省掉结尾的 Attribute,CustomAttribute 写成 [Custom]:

[Custom("类")]
class Machine
{
    public string PropertyNoAttribute { get; set; }

    [Custom("属性")]
    public int Property { get; set; }

    [Custom("字段")]
    public int field;

    [Custom("方法")]
    public void Method() { }
}

用反射读出来

Type type = typeof(Machine);

// 类上的特性
foreach (var item in type.GetCustomAttributes(typeof(CustomAttribute), true))
    Console.WriteLine((item as CustomAttribute)?.Name);

// 字段、属性、方法上的特性
foreach (var item in type.GetFields())
    Console.WriteLine(item.GetCustomAttribute<CustomAttribute>()?.Name);
foreach (var item in type.GetProperties())
    Console.WriteLine(item.GetCustomAttribute<CustomAttribute>()?.Name);
foreach (var item in type.GetMethods())
    Console.WriteLine(item.GetCustomAttribute<CustomAttribute>()?.Name);

?. 的意思是特性不为 null 才取 Name,见 C# 里的各种问号。

输出里有几行空的:属性那一段的空行来自没贴特性的 PropertyNoAttribute;方法那一段的空行来自属性自动生成的 get_、set_ 方法,以及从 object 继承来的 ToString、Equals 等方法。只想要自己声明的方法,可以加绑定标志:

type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
    .Where(m => !m.IsSpecialName); // 去掉属性的 get_/set_

延伸:实际能拿来做什么

  • PLC 点表映射:给模型的属性贴上 [PlcAddress("DB1.DBD0")],通讯层反射遍历属性,按地址批量读写,新增一个点位只要加一个属性。
  • 表格列名和导出:[DisplayName("温度")] 是框架自带的特性,DataGridView 绑定 List<T> 时会用它做列标题;导出 CSV 时也可以按它取表头。
  • 常见的框架特性:[Serializable]、[Obsolete("用 XX 代替")]、[JsonPropertyName("id")](System.Text.Json)、[Required](数据校验)、[Test](单元测试)都是同一套机制。
  • 反射慢,要缓存:GetProperties()、GetCustomAttribute 每次都有开销,在循环或高频通讯里应该只反射一次,把结果存进字典;更快的做法是用表达式树或源生成器(Source Generator)在编译期生成代码,System.Text.Json 和 .NET 8 的 AOT 都在这么做。
  • AOT 和裁剪:发布成 Native AOT 或开启裁剪时,反射可能找不到被裁掉的成员,需要用 [DynamicallyAccessedMembers] 标注。

# C# # 特性 # 反射

原文首发于 blog.csdn.net