一个小例程:

using System;
using System.Linq;
using System.Reflection;

/*
 * 1. 自定义特性定义
 * - AttributeUsage: 定义特性的应用范围和规则
 * - 继承自System.Attribute: 所有自定义特性的基类
 */
[AttributeUsage(
    AttributeTargets.Class |       // 可应用于类
    AttributeTargets.Constructor | // 可应用于构造方法
    AttributeTargets.Field |       // 可应用于字段
    AttributeTargets.Method |      // 可应用于方法
    AttributeTargets.Property,    // 可应用于属性
    AllowMultiple = true           // 允许在同一目标上多次应用
)]
public class DeBugInfo : System.Attribute  // 必须继承自Attribute类
{
    // 2. 私有字段 - 存储核心数据
    private int bugNo;          // Bug编号
    private string developer;   // 开发者名称
    private string lastReview;  // 最后审查日期
    private string message;     // 额外消息(修复递归问题后)

    /*
     * 3. 构造函数
     * - 定位参数:必须通过构造函数传递的参数
     * - 这些参数在应用特性时必须按顺序提供
     */
    public DeBugInfo(int bg, string dev, string d)
    {
        bugNo = bg;       // 设置Bug编号
        developer = dev;  // 设置开发者
        lastReview = d;   // 设置审查日期
    }

    // 4. 只读属性 - 提供私有字段的安全访问
    public int BugNo => bugNo;             // 只读访问Bug编号
    public string Developer => developer;  // 只读访问开发者
    public string LastReview => lastReview;// 只读访问审查日期

    /*
     * 5. 读写属性 - 解决递归问题
     * 原始版本错误:使用属性自身导致无限递归
     * 修复方案:使用私有字段作为存储
     */
    public string Message
    {
        get => message;    // 获取消息内容
        set => message = value; // 设置消息内容
    }
}

/*
 * 6. 应用特性的目标类
 * - 类上应用多个DebugInfo特性(AllowMultiple=true允许)
 * - 混合使用定位参数和命名参数
 */
[DeBugInfo(101,"张三", "2023-10-01", Message = "初始化逻辑")]
[DeBugInfo(202, "李四", "2023-11-15", Message = "性能优化")]
public class Calculator
{
    // 7. 方法上应用特性(只使用定位参数)
    [DeBugInfo(305, "王五", "2023-12-20")]
    public int Add(int a, int b) => a + b;

    // 8. 方法上应用特性(混合定位和命名参数)
    [DeBugInfo(404, "赵六", "2024-01-10", Message = "边界处理")]
    public int Multiply(int a, int b) => a * b;
}

class Program
{
    static void Main()
    {
        // 9. 反射获取类型信息
        Type calcType = typeof(Calculator);

        // 10. 读取类上的所有DebugInfo特性
        Console.WriteLine("【类特性】");
        var classAttrs = calcType.GetCustomAttributes<DeBugInfo>();
        foreach (var attr in classAttrs)
        {
            Console.WriteLine($"Bug#{attr.BugNo} | 开发者:{attr.Developer} " +
                              $"| 审查:{attr.LastReview} | 信息:{attr.Message ?? "(无)"}");
        }

        // 11. 读取所有方法上的特性
        Console.WriteLine("\n【方法特性】");
        foreach (var method in calcType.GetMethods(BindingFlags.Public | BindingFlags.Instance))
        {
            // 12. 获取方法上的DebugInfo特性
            var methodAttrs = method.GetCustomAttributes<DeBugInfo>();
            if (!methodAttrs.Any()) continue; // 跳过无特性的方法

            Console.WriteLine($"方法: {method.Name}");
            foreach (var attr in methodAttrs)
            {
                // 13. 显示特性信息(包含空消息处理)
                Console.WriteLine($"  Bug#{attr.BugNo} | 开发者:{attr.Developer} " +
                                  $"| 审查:{attr.LastReview} | 信息:{attr.Message ?? "(无)"}");
            }
        }
    }
}

输出结果:

【类特性】
Bug#101 | 开发者:张三 | 审查:2023-10-01 | 信息:初始化逻辑
Bug#202 | 开发者:李四 | 审查:2023-11-15 | 信息:性能优化

【方法特性】
方法: Add
  Bug#305 | 开发者:王五 | 审查:2023-12-20 | 信息:(无)
方法: Multiply
  Bug#404 | 开发者:赵六 | 审查:2024-01-10 | 信息:边界处理 

C#代码主要内容解析

提供的C#代码主要演示了自定义特性的定义、应用和反射读取,包含以下核心内容:

1. 自定义特性定义(DeBugInfo
  • 继承与元数据
    DeBugInfo 继承自 System.Attribute,使用 [AttributeUsage] 指定可应用于类、构造器、字段、方法和属性(AllowMultiple=true 允许多次应用)。

  • 数据结构

    • 私有字段:bugNo(Bug编号)、developer(开发者)、lastReview(审查日期)、message(消息)。

    • 构造函数:接受定位参数(int bg, string dev, string d)。

    • 属性:BugNoDeveloperLastReview 为只读;Message 为读写(修复了递归问题)。

2. 特性应用示例(Calculator 类)
  • 类级别应用
    应用两个 DeBugInfo 特性

    C#

    [DeBugInfo(101,"张三","2023-10-01", Message="初始化逻辑")] [DeBugInfo(202,"李四","2023-11-15", Message="性能优化")]

  • 方法级别应用

    • Add 方法:使用定位参数 [DeBugInfo(305,"王五","2023-12-20")]

    • Multiply 方法:混合定位和命名参数 [DeBugInfo(404,"赵六","2024-01-10", Message="边界处理")]

3. 反射读取特性(Program.Main 方法)
  • 类特性读取
    通过 typeof(Calculator).GetCustomAttributes<DeBugInfo>() 获取所有类特性,遍历输出信息(如Bug编号、开发者)。

  • 方法特性读取
    使用 GetMethods() 获取公共实例方法,结合 GetCustomAttributes<DeBugInfo>()Any() 跳过无特性方法:

  • C#

    var methodAttrs = method.GetCustomAttributes<DeBugInfo>(); if (!methodAttrs.Any()) continue; // 关键过滤逻辑

  • 输出处理
    对每个特性打印格式化信息(含空消息处理 attr.Message ?? "(无)")。

using System;
using System.Reflection;

namespace c.biancheng.net
{
    //要分配给类及其成员的自定义属性错误修正程序
    [AttributeUsage(
        AttributeTargets.Class|
        AttributeTargets.Constructor|
        AttributeTargets.Field|
        AttributeTargets.Method|
        AttributeTargets.Property,
        AllowMultiple =true)]

    public class DeBugInfo : System.Attribute
    {
        private int bugNo;
        private string developer;
        private string lastReview;
        public string message;

        public DeBugInfo(int bg,string dev,string d) {
            this.bugNo = bg;
            this.developer = dev;
            this.lastReview = d;
        } 
        public int BugNo
        {
            get
            {
                return bugNo;
            }
        }
        public string Developer
        {
            get
            {
                return developer;
            }
        }
        public string LastReview
        {
            get
            {
                return lastReview;
            }
        }

        public string Message
        {
            get
            {
                return message;
            }
            set
            {
                message = value;
            }
        }
        
    }

    [DeBugInfo(45,"Zara Ali","12/8/2012",Message ="返回类型不匹配")]
    [DeBugInfo(49,"JUha Ali","10/10/2021",Message ="变量未使用")]

    class Rectangle
    {
        //成员变量
        protected double length;
        protected double width;

        public Rectangle(double length, double width)
        {
            this.length = length;
            this.width = width;
        }
        [DeBugInfo(55,"Zara Ali","19/10/2012",Message ="返回值类型不匹配")]

        public double GetArea()
        {
            return length*width;
        }
        [DeBugInfo(56,"Zara Ali","19/10/2012")]
        public void Display()
        {
            Console.WriteLine("长: {0}", length);
            Console.WriteLine("宽: {0}", width);
            Console.WriteLine("面积: {0}", GetArea());
        }
    }

    class Demo
    {
        static void Main(string[] args)
        {
            Rectangle r = new Rectangle(4.5, 7.5);
            r.Display();
            Type type = typeof(Rectangle);

            //遍历Rectangle
            foreach (Object attributes in type.GetCustomAttributes(false))
            {
                DeBugInfo dbi = (DeBugInfo)attributes;
                if (null != dbi)
                {
                    Console.WriteLine("Bug 编号: {0}", dbi.BugNo);
                    Console.WriteLine("开发者: {0}", dbi.Developer);
                    Console.WriteLine("上次审核时间: {0}", dbi.LastReview);
                    Console.WriteLine("评论: {0}", dbi.Message);
                }
            }

            // 遍历函数属性
            foreach (MethodInfo m in type.GetMethods())
            {
                foreach (Attribute a in m.GetCustomAttributes(true))
                {
                    DeBugInfo dbi = (DeBugInfo)a;
                    if (null != dbi)
                    {
                        Console.WriteLine("Bug 编号: {0}, 函数名: {1}", dbi.BugNo, m.Name);
                        Console.WriteLine("开发者: {0}", dbi.Developer);
                        Console.WriteLine("上次审核时间: {0}", dbi.LastReview);
                        Console.WriteLine("评论: {0}", dbi.Message);
                    }
                }
            }
            Console.ReadLine();

        }
    }
}

运行结果:

长: 4.5
宽: 7.5
面积: 33.75
Bug 编号: 45
开发者: Zara Ali
上次审核时间: 12/8/2012
评论: 返回类型不匹配
Bug 编号: 49
开发者: JUha Ali
上次审核时间: 10/10/2021
评论: 变量未使用
Bug 编号: 55, 函数名: GetArea
开发者: Zara Ali
上次审核时间: 19/10/2012
评论: 返回值类型不匹配
Bug 编号: 56, 函数名: Display
开发者: Zara Ali
上次审核时间: 19/10/2012
评论:
Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.Runtime.CompilerServices.NullableContextAttribute' to type 'c.biancheng.net.DeBugInfo'.
   at c.biancheng.net.Demo.Main(String[] args) in D:\Project_Doc\ConsoleApp1\ConsoleApp1\Program.cs:line 133

代码解释:该代码通过反射遍历类型(Rectangle类)及其方法的自定义特性(DeBugInfo)。
代码分为两部分:遍历类上的特性、遍历每个方法上的特性。

第一部分:遍历类特性

  • 使用 type.GetCustomAttributes(false) 获取直接应用于类的特性(不搜索继承链)。

  • 将每个特性强制转换为 DeBugInfo 类型,并输出相关信息。

第二部分:遍历方法特性

  • 遍历类型的所有方法(type.GetMethods() 默认返回公共实例方法)。

  • 对每个方法,使用 m.GetCustomAttributes(true) 获取所有特性(包括继承的特性)。

  • 同样将特性转换为 DeBugInfo 并输出,同时输出方法名。

注意:代码中使用了两个循环嵌套来遍历方法和其特性。

typeof 在编译时确定类型(不需要实例) Type t1 = typeof(Rectangle);

GetType() 在运行时确定实际类型(需要实例)

// typeof 在编译时确定类型(不需要实例)
Type t1 = typeof(Rectangle);  

// GetType() 在运行时确定实际类型(需要实例)
Rectangle rect = new Rectangle();
Type t2 = rect.GetType();  

索引器的重载

索引器可以被重载,而且在声明索引器时也可以带有多个参数,每个参数可以是不同的类型。另外,索引器中的索引不必是整数,也可以是其他类型,例如字符串类型。

using System;

// 创建一个类来存储字符串键值对
public class StringIndexerExample
{
    private string[,] data = new string[3, 2]; // 存储数据的二维数组

    public StringIndexerExample()
    {
        // 初始化一些数据
        data[0, 0] = "Apple";
        data[0, 1] = "Red";
        data[1, 0] = "Banana";
        data[1, 1] = "Yellow";
        data[2, 0] = "Grape";
        data[2, 1] = "Purple";
    }

    // 索引器重载1:使用整数作为索引
    public string this[int index]
    {
        get
        {
            if (index >= 0 && index < data.GetLength(0))
            {
                return data[index, 0]; // 返回水果名称
            }
            else
            {
                throw new IndexOutOfRangeException("索引超出范围");
            }
        }
    }

    // 索引器重载2:使用字符串作为索引
    public string this[string key]
    {
        get
        {
            for (int i = 0; i < data.GetLength(0); i++)
            {
                if (data[i, 0].Equals(key, StringComparison.OrdinalIgnoreCase))
                {
                    return data[i, 1]; // 返回颜色
                }
            }
            return null; // 如果没有找到,则返回null
        }
    }

    // 索引器重载3:使用两个整数作为索引
    public string this[int row, int column]
    {
        get
        {
            if (row >= 0 && row < data.GetLength(0) && column >= 0 && column < data.GetLength(1))
            {
                return data[row, column];
            }
            else
            {
                throw new IndexOutOfRangeException("索引超出范围");
            }
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        StringIndexerExample example = new StringIndexerExample();

        // 使用整数索引访问
        Console.WriteLine("第一个水果: " + example[0]);

        // 使用字符串索引访问
        Console.WriteLine("Banana的颜色: " + example["Banana"]);

        // 使用两个整数索引访问
        Console.WriteLine("第二个元素的第二列值: " + example[1, 1]);
    }
}

更多推荐