特性与反射
本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 44 课,共 70 课。
属性是附加到类、方法或属性等代码元素上的元数据标签。它们不会直接改变代码的执行方式,但会提供可在运行时读取的信息。反射是读取这些元数据的机制。
要应用属性,请将它放在目标元素上方的方括号中:
[Obsolete("Use NewMethod instead")]
public void OldMethod() { }
[Serializable]
public class Person
{
public string Name { get; set; }
}你可以通过继承 Attribute 来创建自定义属性:
public class AuthorAttribute : Attribute
{
public string Name { get; }
public AuthorAttribute(string name) => Name = name;
}
[Author("John")]
public class Calculator { }你可以使用 System.Reflection 命名空间在运行时检查类型并读取属性:
Type type = typeof(Calculator);
var attr = type.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(attr.Name); // John你还可以查找方法和属性,并动态调用它们:
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
Console.WriteLine(method.Name);属性和反射为许多框架提供了强大功能:序列化库用它们来控制 JSON 输出,测试框架用它们来查找测试方法,依赖注入容器则用它们来自动连接服务。
挑战
简单我们来构建一个插件发现系统,使用自定义属性和反射,自动查找并描述标记为插件的类。这类似于真实框架在运行时发现组件、测试方法或 API 端点的方式。
你将把代码分到三个文件中:
PluginAttribute.cs:在PluginSystem命名空间中创建一个名为PluginAttribute的自定义属性。这个属性应该:- 继承
Attribute - 有两个只读属性:
Name(字符串)和Version(字符串) - 通过构造函数接收这两个值
- 继承
Plugins.cs:在PluginSystem命名空间中创建几个插件类。其中一些类应使用你的PluginAttribute标记,另一个不标记:ImageProcessor:使用[Plugin("Image Processor", "1.0")]标记DataExporter:使用[Plugin("Data Exporter", "2.5")]标记HelperUtility:不使用任何属性标记(这只是一个普通类)
Program.cs:在主文件中使用反射,查找所有带有PluginAttribute的类。对每个找到的插件,打印其信息。你需要:- 从当前程序集获取所有类型
- 检查每个类型是否带有
PluginAttribute - 提取并显示该属性的
Name和Version属性值
使用 System.Reflection 检查类型并获取自定义属性。GetCustomAttribute<T>() 方法在属性不存在时会返回 null,因此你可以用它筛选出真正的插件类。
按以下格式每行打印一个找到的插件:
Plugin: {Name} (v{Version})插件应按以下顺序打印:先打印 ImageProcessor,再打印 DataExporter。由于 HelperUtility 类没有该属性,因此不应出现在输出中。
预期输出:
Plugin: Image Processor (v1.0)
Plugin: Data Exporter (v2.5)这个挑战展示了框架如何使用属性作为标记,并通过反射发现这些标记。这项技术也用于依赖注入容器、序列化库和测试框架!
亲手试试
using System;
using System.Reflection;
using PluginSystem;
class Program
{
public static void Main(string[] args)
{
// TODO: 获取当前程序集中的所有类型
// 提示:使用 Assembly.GetExecutingAssembly().GetTypes()
// TODO: 遍历每个类型,检查它是否有 PluginAttribute 属性
// 提示:使用 type.GetCustomAttribute<PluginAttribute>()
// TODO: 如果该属性存在,按以下格式打印插件信息:
// Plugin: {Name} (v{Version})
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C# 在线编译器