C# 自定义特性(Attribute)+ 反射读取特性 +WinForm 自定义控件常用特性

一、核心概念

1. 什么是特性

特性(Attribute)是标记代码元素的描述信息(类、方法、字段、属性)。

作用:给代码附加额外元数据,编译时标记、运行时通过反射读取,实现扩展功能。

系统自带特性:Obsolete、Conditional

本课重点:自己定义特性 + 反射解析特性数据

2. 自定义特性固定步骤(必考背诵)

步骤1:创建类,继承 Attribute 基类(规范类名以 Attribute 结尾)

步骤2:添加 [AttributeUsage] 限制特性使用规则

步骤3:定义属性、构造函数接收参数

步骤4:将自定义特性标记在 类/方法 上

步骤5:通过反射 Type / MethodInfo 读取特性信息


二、AttributeUsage 限制规则(重点)

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple =true,Inherited = false)]

参数详解

1. AttributeTargets:限定特性可以标记在哪些位置

Class:标记类

Method:标记方法

Field:标记字段

Property:标记属性

All:所有位置

2. AllowMultiple:是否允许重复标记同一个元素

true:允许多次标记(本案例 Test1 标记了两个 MyAttribute)

false:同一个位置只能标记一次

3. Inherited:是否允许子类继承该特性

true:子类继承父类特性

false:子类不继承(本案例关闭继承)


三、自定义特性类完整代码解析

//限制:只能标记类、方法,允许多次标记,不允许继承 [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple =true,Inherited = false)] internal class MyAttribute:Attribute { //自定义特性存储的字段信息 public string Version { get; set; } //版本号 public string Name { get; set; } //作者名称 public string Message { get; set; } //功能描述 public string CallTime { get; set; } //编写时间 //构造函数:给特性赋值 public MyAttribute(string version, string name, string message, string callTime) { Version = version; Name = name; Message = message; CallTime = callTime; } }

规范

自定义特性必须继承 Attribute

类名规范xxxAttribute

构造函数用于接收标记时传入的参数


四、标记特性(使用特性)

1、标记在类上

[My("1.0","Jack","入口类","2026-7-1")] internal class Program

2、多次标记在方法上(AllowMultiple=true 生效)

[MyAttribute("1.0", "Jack", "Test1方法", "2026-7-1")] [MyAttribute("2.0", "Rose", "Test1方法", "2026-7-2")] public static void Test1() { Console.WriteLine("Test1"); }

因为设置了AllowMultiple = true,同一个方法可以挂载多个特性


五、反射读取特性完整流程(核心考点)

完整四步流程(必背)

第一步:获取当前类的 Type 类型

Type t1 = typeof(Program);

第二步:通过Type获取指定方法的元数据 MethodInfo

MethodInfo info = t1.GetMethod("Test1");

第三步:获取该方法上所有自定义特性

object[] attrs = info.GetCustomAttributes(typeof(MyAttribute),false);

第四步:遍历读取特性中的自定义属性

反射代码逐行解析

//获取Program类的类型 Type t1 = typeof(Program); //通过反射拿到Test1方法信息 MethodInfo info = t1.GetMethod("Test1"); //判空防止报错 if (info!=null) { //反射调用方法 info.Invoke(null,null); } //获取该方法上所有 MyAttribute 特性 //参数2 false:不向父类查找特性 object[] attrs = info.GetCustomAttributes(typeof(MyAttribute),false); //遍历所有特性,读取自定义数据 foreach (MyAttribute attr in attrs) { Console.WriteLine(attr.Name+":"+attr.Message); }

六、运行结果

Test1

Jack:Test1方法

Rose:Test1方法


七、重难点总结(考试简答)

1、自定义特性必须满足什么条件?

必须继承 Attribute 类,配合 AttributeUsage 声明使用范围。

2、AllowMultiple 作用?

true 允许同一个位置多次标记同一个特性。

3、Inherited 作用?

控制子类是否继承父类的特性。

4、如何读取自定义特性?

通过反射 Type / MethodInfo 的 GetCustomAttributes 方法获取。


八、高频易错点

1、不加 AttributeUsage,特性默认只能标记一次、不可继承、适用性窄

2、多次标记特性必须开启 AllowMultiple = true

3、特性数据必须通过反射读取,直接无法访问

4、GetCustomAttributes 第二个参数 false = 不搜索父类


九、完整版可直接抄写代码

MyAttribute

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace _6自定义特性 { // 1自定义特性步骤 // 1 定义一个类继承于Attribute ,类最好以Attribute单词进行结尾 // 2 通过AttributeUsage特性规划自定义特性的特点 // AttributeTargets.Clas 把特性放置到类的前面 // Method 把特性放置到方法的前面 // Field 把特性放置到字段的前面 //Property 把特性放置到属性的前面 //AllowMultiple 允许特性是否可以多次使用 //Inherited 是否能够被派生类进行继承 [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple =true,Inherited = false)] internal class MyAttribute:Attribute { public string Version { get; set; }// 版本 public string Name { get; set; } // 名称 public string Message { get; set; }// 描述信息 public string CallTime { get; set; } //时间 public MyAttribute(string version, string name, string message, string callTime) { Version = version; Name = name; Message = message; CallTime = callTime; } } }

Program

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace _6自定义特性 { [My("1.0","Jack","入口类","2026-7-1")] internal class Program { static void Main(string[] args) { //Test1(); //直接调用这个方法,必须先保证这个方法存在 //引出反射获取特性等等信息 Type type = typeof(int);//获取int的类型 Console.WriteLine(type); //System.Int32 Type t1 = typeof(Program);//获取program类型 Console.WriteLine(t1); //_6自定义特性.Program MethodInfo info = t1.GetMethod("Test1"); //通过反射获取Test1方法 if (info!=null) { info.Invoke(null,null);//调用方法 } //获取方法的特性集合 参数1 特性的的类型 参数2 写成false 不会去子类去搜索特性 object[] attrs = info.GetCustomAttributes(typeof(MyAttribute),false); foreach (MyAttribute attr in attrs) { Console.WriteLine(attr.Name+":"+attr.Message); } // 1 先获取的类的类型 Type t1 = typeOf(program) // 2 再获取方法 MethodInfo info = t1.GetMethod("Test1"); // 3 再根据方法获取方法上特性 object[] attrs = info.GetCustomAttributes(typeof(MyAttribute),false); // 4 遍历特性上每一个属性 } [MyAttribute("1.0", "Jack", "Test1方法", "2026-7-1")] [MyAttribute("2.0", "Rose", "Test1方法", "2026-7-2")] public static void Test1() { Console.WriteLine("Test1"); } public static void Test2() { } } }

WinForm 自定义控件常用特性

一、课程知识点概述

在 WinForm 开发中,我们可以对自定义用户控件(UserControl)的公开属性添加系统内置特性。

这类特性专门用于控制 Visual Studio 右侧【属性面板】的展示样式、分类、描述、默认值、废弃状态,是规范自定义控件、封装通用组件的核心知识点。

本节课包含两大核心内容:

1. 五大常用控件属性特性:Browsable、Category、Description、DefaultValue、Obsolete

2. 纯代码手写自定义控件(无拖拽设计器)+ DesignerCategory 特性说明


二、五大核心特性 逐一点精讲(必考)

1、[Browsable(bool)] 可见性特性

作用:控制自定义属性是否在 VS 设计器的【属性面板】中显示。

Browsable(true):默认值,属性公开显示,设计师可手动修改。

Browsable(false):属性隐藏,属性面板不可见,仅后台代码可调用。

2、[Category("分类名称")] 分类特性

作用:对自定义属性进行分组归类。

系统自带分类:外观、布局、行为、数据。

可自定义任意分类名,相同分类的属性会在面板中统一收纳,整洁规整。

案例:Account 归为「佛得角」、Password 归为「阿根廷」。

3、[Description("描述文本")] 注释特性

作用:鼠标悬浮在属性上时,展示功能说明文字。

用于标注属性用途,提升控件可读性,方便团队协作开发。

4、[DefaultValue("默认值")] 默认值特性(重点)

核心功能

① 标记属性的初始默认值;

视觉区分修改状态:属性值 = 默认值 → 普通字体;属性值被修改 → 字体自动加粗。

注意:仅为设计器视觉标记,不会自动给属性赋值,真正初始化赋值需要代码实现。

5、[Obsolete("提示信息")] 废弃特性

作用:标记当前属性过时、不推荐使用。

效果:编译输出警告、属性面板标注废弃提示,强制提示开发者更换新属性。


三、可视化自定义控件代码解析(UserControl1)

功能说明

该控件为拖拽式自定义用户控件,内置两个文本框,封装 Account(账号)、Password(密码)两个公开属性,属性与文本框双向绑定。

完整代码+逐行解析

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace _7_自定义控件常用的特性 { public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); } //控制属性在属性面板显示 [Browsable(true)] //自定义属性分类 [Category("佛得角")] //属性功能描述 [Description("账号的内容")] //设置属性默认值,区分是否被修改 [DefaultValue("佛得角")] //账号属性:双向绑定textBox1文本 public string Account { get { return textBox1.Text; } set { textBox1.Text = value; } } //多特性合并写法,效果完全一致 [Description("密码"),Category("阿根廷"),DefaultValue("123"),Obsolete("不建议使用此属性")] //密码属性:双向绑定textBox2文本,标记为废弃 public string Password { get { return textBox2.Text; } set { textBox2.Text = value; } } } }

代码核心逻辑

1、属性get:读取文本框内容,对外暴露值;

2、属性set:外部赋值,同步修改文本框显示内容;

3、通过多特性组合,完全自定义属性在设计器的展示效果。


四、纯代码手写自定义控件解析(UserLoin)

1、核心特性说明

[DesignerCategory("Code")]

作用:标记当前控件为纯代码控件,告知 VS 无需开启可视化拖拽设计器,避免报错,纯手写布局专用。

本案例注释该特性,可按需开启。

2、手写控件原理

1、自定义类继承 UserControl 用户控件基类;

2、手动声明 Label、TextBox、Button 等子控件;

3、重写 InitializeComponent 方法,手动设置控件坐标、大小、文本、索引;

4、通过 Controls.Add() 将子控件挂载到自定义控件中;

5、SuspendLayout/ResumeLayout 优化批量加载控件性能。

完整手写控件代码

using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace _7_自定义控件常用的特性 { // 关键特性:告诉VS这是纯代码类,不是可拖拽设计界面 //[DesignerCategory("Code")] internal class UserLoin:UserControl { //手动声明所有子控件 private Label label1; private TextBox textBox1; private Label label2; private Button button1; private TextBox textBox2; public UserLoin() { InitializeComponent(); } //纯手写布局方法 private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.textBox1 = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.textBox2 = new System.Windows.Forms.TextBox(); this.button1 = new System.Windows.Forms.Button(); this.SuspendLayout(); // 账号Label this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(97, 51); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(70, 24); this.label1.TabIndex = 0; this.label1.Text = "账号:"; // 账号输入框 this.textBox1.Location = new System.Drawing.Point(207, 51); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(244, 35); this.textBox1.TabIndex = 1; // 密码Label this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(97, 128); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(70, 24); this.label2.TabIndex = 0; this.label2.Text = "密码:"; // 密码输入框 this.textBox2.Location = new System.Drawing.Point(207, 125); this.textBox2.Name = "textBox2"; this.textBox2.Size = new System.Drawing.Size(244, 35); this.textBox2.TabIndex = 1; // 登录按钮 this.button1.Location = new System.Drawing.Point(207, 240); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(131, 52); this.button1.TabIndex = 2; this.button1.Text = "登录"; this.button1.UseVisualStyleBackColor = true; // 将所有子控件添加到自定义控件中 this.Controls.Add(this.button1); this.Controls.Add(this.textBox2); this.Controls.Add(this.label2); this.Controls.Add(this.textBox1); this.Controls.Add(this.label1); this.Name = "UCLogin"; this.Size = new System.Drawing.Size(585, 359); this.ResumeLayout(false); this.PerformLayout(); } } }

五、特性组合使用规则

1、多个特性可以分行书写,也可以逗号同行书写,效果完全一致;

2、所有控件特性仅在设计器界面生效,不影响程序运行逻辑;

3、DefaultValue 只做视觉标记,不会覆盖代码初始化值;

4、Obsolete 只产生编译警告,不阻断程序运行。


六、考试/作业 核心背诵总结

1. Browsable:控制属性面板是否显示自定义属性

2. Category:自定义属性分类,规整设计器界面

3. Description:为属性添加悬浮功能注释

4. DefaultValue:设置默认值,修改后字体加粗提示

5. Obsolete:标记属性废弃,编译弹出警告

6. DesignerCategory("Code"):声明纯手写代码控件,禁用可视化设计器