Menu
CoddyTech

特性与反射

本课属于 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 输出,测试框架用它们来查找测试方法,依赖注入容器则用它们来自动连接服务。

challenge icon

挑战

简单

我们来构建一个插件发现系统,使用自定义属性和反射,自动查找并描述标记为插件的类。这类似于真实框架在运行时发现组件、测试方法或 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})
        
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C# 在线编译器