c#软件开发学习笔记--特性、反射
C#特性(Attribute)与反射(Reflection)
一、特性详解
1.特性概述和定义方式
特性(Attribute)是用于在运行时传递程序中各种元素(类、方法、结构、枚举、组件等)行为信息的声明性标签。它通过方括号 [ ] 来描述,可以为程序添加元数据(Metadata)。
元数据:保存在程序集中有关程序及其类型的数据,主要用来描述C#中各种元素(类、方法、构造函数、属性等)。
特性本质上是一个类,必须满足以下条件:
// 1. 命名建议以Attribute结尾,使用大驼峰命名
// 2. 必须继承基类Attribute
// 3. 使用AttributeUsage特性来控制自定义特性的应用范围
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple = true,
Inherited = false)]
internal class MyAttribute : Attribute
{
public string Version { get; set; }
public string Name { get; set; }
public MyAttribute(string version, string name)
{
Version = version;
Name = name;
}
}
2. AttributeUsage特性
AttributeUsage 用于描述如何使用自定义特性,规定特性可应用到的项目类型:
| 参数 | 说明 | 默认值 |
|---|---|---|
validon |
特性可被放置的语言元素(AttributeTargets枚举值组合) |
AttributeTargets.All |
AllowMultiple |
是否允许多次使用该特性 | false |
Inherited |
是否可被派生类继承 | false |
AttributeTargets 常用枚举值:
| 值 | 说明 |
|---|---|
Class |
类 |
Method |
方法 |
Field |
字段 |
Property |
属性 |
Constructor |
构造函数 |
Event |
事件 |
Interface |
接口 |
3. 内置特性
3.1 SerializableAttribute(序列化特性)
作用:标记类型可以被序列化,即可以将对象转换为字节流进行存储或传输。
示例:
[Serializable] // 标记类可序列化
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
使用场景:对象持久化、网络传输、深拷贝等。
3.2 ConditionalAttribute(条件特性)
作用:标记条件方法,其执行依赖于指定的预处理标识符。用于条件编译。
#define Debug // 定义编译符号
#undef Debug // 取消编译符号
using System.Diagnostics;
[ConditionalAttribute("Debug")] // 条件编译特性
public static void T1()
{
Console.WriteLine("1111");
}
工作原理:
- 当定义了
Debug编译符号时,T1()方法会被编译进项目 - 当未定义
Debug编译符号时,T1()方法调用会被编译器忽略
应用场景:调试日志输出、测试代码隔离等。
3.3 ObsoleteAttribute(废弃特性)
作用:标记不应被使用的程序实体,通知编译器丢弃某个特定的目标元素。
// 参数1:废弃提示信息
// 参数2:是否编译错误(true=错误,false=警告)
[Obsolete("提示Test1有可能以后会被丢弃,会被test2方法进行替代", false)]
static void Test1()
{
Console.WriteLine("Test1");
}
语法:
[Obsolete(message)] // 仅警告
[Obsolete(message, iserror)] // 警告或错误
应用场景:API版本升级、方法废弃提示等。
3.4 程序集级别特性(了解)
程序集级别特性用于描述整个程序集的元数据,通常定义在 AssemblyInfo.cs 文件中。
// 程序集标题
[assembly: AssemblyTitle("6自定义特性")]
// 程序集描述
[assembly: AssemblyDescription("")]
// 程序集配置
[assembly: AssemblyConfiguration("")]
// 公司名称
[assembly: AssemblyCompany("微软中国")]
// 产品名称
[assembly: AssemblyProduct("6自定义特性")]
// 版权信息
[assembly: AssemblyCopyright("Copyright © 微软中国 2026")]
// 商标
[assembly: AssemblyTrademark("")]
// 文化信息
[assembly: AssemblyCulture("")]
// COM可见性
[assembly: ComVisible(false)]
// GUID(用于COM类型库)
[assembly: Guid("e243efae-b57c-4bfd-bfdf-6769e4696177")]
// 程序集版本(主版本.次版本.生成号.修订号)
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
程序集级别特性说明:
| 特性 | 作用 |
|---|---|
AssemblyTitle |
程序集标题 |
AssemblyDescription |
程序集描述信息 |
AssemblyConfiguration |
程序集配置(如Debug/Release) |
AssemblyCompany |
公司名称 |
AssemblyProduct |
产品名称 |
AssemblyCopyright |
版权信息 |
AssemblyTrademark |
商标信息 |
AssemblyCulture |
文化/语言信息 |
ComVisible |
是否对COM组件可见 |
Guid |
COM类型库的唯一标识符 |
AssemblyVersion |
程序集版本号 |
AssemblyFileVersion |
文件版本号 |
4. 自定义特性
[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;
}
}
[My("1.0", "Jack", "入口类", "2026-7-1")]
internal class Program
{
[MyAttribute("1.0", "Jack", "Test1方法", "2026-7-1")]
[MyAttribute("2.0", "Rose", "Test1方法", "2026-7-2")] // AllowMultiple=true 允许多次使用
public static void Test1()
{
Console.WriteLine("Test1");
}
}
注意:特性名称使用时可省略 Attribute 后缀(My 等价于 MyAttribute)。
5. 自定义控件常用特性
在Windows Forms自定义控件开发中,常用以下特性来控制属性面板的显示:
[Browsable(true)] // 是否在属性面板中展示(true=展示,false=隐藏)
[Category("佛得角")] // 属性分类名称
[Description("账号的内容")] // 属性描述信息
[DefaultValue("佛得角")] // 默认值(等于默认值显示普通文本,否则加粗)
public string Account
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
// 多个特性可用逗号分隔
[Description("密码"), Category("阿根廷"), DefaultValue("123"), Obsolete("不建议使用此属性")]
public string Password
{
get { return textBox2.Text; }
set { textBox2.Text = value; }
}
控件特性说明:
| 特性 | 作用 |
|---|---|
Browsable |
控制属性是否在属性窗口中出现 |
Category |
为属性分类,默认放到"杂项" |
Description |
给属性添加描述信息 |
DefaultValue |
设置属性默认值,VS属性窗口中与默认值不同会显示粗体 |
DesignerCategory("Code") |
告诉VS这是纯代码类,不是可拖拽设计界面 |
二、反射详解
1.反射概述
反射是指在程序运行中,查看、操作自身或者其他程序集的元数据的各种信息(类、方法、属性、变量、对象等)的行为。反射允许你在运行时检查和操作程序集、类型和对象的信息。
反射提供以下核心能力:
- 在运行时查看特性(Attribute)信息
- 审查集合中的各种类型,以及实例化这些类型
- 延迟绑定方法和属性
- 在运行时创建新类型并执行任务
2. 反射常用类
| 类 | 命名空间 | 功能 |
|---|---|---|
Type |
System |
表示类型声明(类、接口、数组、值类型等) |
Assembly |
System.Reflection |
表示程序集,可加载和访问程序集信息 |
MethodInfo |
System.Reflection |
表示方法的元数据 |
PropertyInfo |
System.Reflection |
表示属性的元数据 |
FieldInfo |
System.Reflection |
表示字段的元数据 |
ConstructorInfo |
System.Reflection |
表示构造函数的元数据 |
EventInfo |
System.Reflection |
表示事件的元数据 |
MemberInfo |
System.Reflection |
所有成员类型的抽象基类 |
3. 获取类型(Type)
方式一:使用 typeof() 操作符(编译时确定)
Type type = typeof(int); // System.Int32
Type type = typeof(Program); // 命名空间.Program
方式二:使用 GetType() 方法(运行时获取)
object obj = new Student();
Type type = obj.GetType(); // 获取对象的实际类型
4.创建实例的三种方式:
// 方法1:直接使用CreateInstance带参数
object[] constructorParams1 = new object[] { "admin", "123456" };
object account1 = assembly.CreateInstance("ClassLibrary.Account", false,
BindingFlags.Default, null, constructorParams1, null, null);
// 方法2:使用构造函数信息
ConstructorInfo constructor = accountType.GetConstructor(new Type[] { typeof(string), typeof(string) });
object[] constructorParams2 = new object[] { "user", "pass" };
object account2 = constructor.Invoke(constructorParams2);
// 方法3:使用Activator(更简单)
object account3 = Activator.CreateInstance(accountType, "admin", "123456");
4. 使用反射调用方法
// 1. 获取类型
Type t1 = typeof(Program);
// 2. 获取方法
MethodInfo info = t1.GetMethod("Test1");
// 3. 调用方法(参数1:调用对象,静态方法传null;参数2:方法参数数组)
if (info != null)
{
info.Invoke(null, null);
}
5. 使用反射获取特性
// 获取方法上的特性集合
// 参数1:特性类型;参数2:是否搜索继承链
object[] attrs = info.GetCustomAttributes(typeof(MyAttribute), false);
foreach (MyAttribute attr in attrs)
{
Console.WriteLine(attr.Name + ":" + attr.Message);
}
6. 加载外部程序集(Assembly)
// 1. 定义DLL路径
string path = Path.Combine(Environment.CurrentDirectory, "../../libs/ClassLibrary1.dll");
// 2. 加载程序集
Assembly sbly = Assembly.LoadFile(path);
// 3. 创建类实例(参数:命名空间.类名)
object class1 = sbly.CreateInstance("ClassLibrary1.Class1");
// 4. 获取类型
Type type = class1.GetType();
// 5. 获取所有成员
MemberInfo[] arr = type.GetMembers();
foreach (MemberInfo item in arr)
{
// 获取属性
if (item.MemberType == MemberTypes.Property)
{
PropertyInfo info = item as PropertyInfo;
Console.WriteLine(info.GetValue(class1));
}
// 获取方法
if (item.MemberType == MemberTypes.Method)
{
MethodInfo info = item as MethodInfo;
if (info.Name == "Test1")
{
info.Invoke(class1, null);
}
}
}
7. 反射操作字段和属性
操作字段示例:
// 获取类型
Type type = typeof(Student);
// 通过程序集创建实例
Assembly assembly = Assembly.Load("当前程序集名称");
object instance = assembly.CreateInstance("命名空间.Student");
// 获取公开字段
FieldInfo fieldInfo = type.GetField("myId");
int myId = (int)fieldInfo.GetValue(instance);
Console.WriteLine(myId);
// 设置字段值
fieldInfo.SetValue(instance, 123);
// 获取所有公开字段
FieldInfo[] fieldInfos = type.GetFields();
foreach (var field in fieldInfos)
{
Console.WriteLine(field.Name + ": " + field.GetValue(instance));
}
操作属性示例:
// 获取属性
PropertyInfo propertyInfo = type.GetProperty("Name");
// 设置属性值
propertyInfo.SetValue(instance, "张三");
// 获取属性值
string name = (string)propertyInfo.GetValue(instance);
Console.WriteLine(name);
// 获取所有公开属性
PropertyInfo[] propertyInfos = type.GetProperties();
foreach (var prop in propertyInfos)
{
Console.WriteLine(prop.Name);
if (prop.Name == "Name")
{
prop.SetValue(instance, "李四");
}
}
8. 使用BindingFlags访问私有成员
默认情况下,反射只能访问公共成员。要访问私有成员,需要使用 BindingFlags 参数。
// 获取私有字段
FieldInfo privateField = type.GetField("_privateField",
BindingFlags.Instance | BindingFlags.NonPublic);
// 获取私有方法
MethodInfo privateMethod = type.GetMethod("PrivateMethod",
BindingFlags.Instance | BindingFlags.NonPublic);
// 获取静态成员
MethodInfo staticMethod = type.GetMethod("StaticMethod",
BindingFlags.Static | BindingFlags.Public);
常用BindingFlags组合:
| 组合 | 说明 |
|---|---|
BindingFlags.Instance | BindingFlags.Public |
实例公共成员 |
BindingFlags.Instance | BindingFlags.NonPublic |
实例私有成员 |
BindingFlags.Static | BindingFlags.Public |
静态公共成员 |
BindingFlags.Static | BindingFlags.NonPublic |
静态私有成员 |
BindingFlags.Public | BindingFlags.NonPublic |
所有访问级别成员 |
9. 反射获取嵌套类型
// 获取嵌套类型
Type nestedType = type.GetNestedType("NestedClassName");
// 创建嵌套类型实例
object nestedInstance = assembly.CreateInstance(nestedType.FullName);
10. 完整反射示例:Student类操作
目标类定义:
public class Student
{
// 嵌套类型
public class SmallStudent
{
public string Name { get; set; } = "小学生";
}
// 字段
public int myId = 10;
public string MyName = "默认姓名";
private int _id;
// 属性
public int Id { get; set; }
public string Name { get; set; }
// 方法
[Obsolete("过时的方法")]
public void Method2(string a, string b)
{
Console.WriteLine($"公开方法: {a}{b}");
}
public static void Method3()
{
Console.WriteLine("静态方法");
}
}
反射操作完整流程:
// 1. 获取类型
Type studentType = typeof(Student);
// 2. 获取程序集并创建实例
Assembly assembly = Assembly.Load("当前程序集名称");
object student = assembly.CreateInstance("命名空间.Student");
// 3. 获取并操作字段
FieldInfo fieldInfo = studentType.GetField("myId");
int id = (int)fieldInfo.GetValue(student);
Console.WriteLine($"字段值: {id}");
// 4. 获取所有字段
FieldInfo[] fields = studentType.GetFields();
foreach (var field in fields)
{
Console.WriteLine($"字段: {field.Name}, 值: {field.GetValue(student)}");
}
// 5. 获取并操作属性
PropertyInfo propInfo = studentType.GetProperty("Name");
propInfo.SetValue(student, "张三");
string name = (string)propInfo.GetValue(student);
Console.WriteLine($"属性值: {name}");
// 6. 获取所有属性
PropertyInfo[] props = studentType.GetProperties();
foreach (var prop in props)
{
Console.WriteLine($"属性: {prop.Name}");
}
// 7. 获取并调用方法
MethodInfo method = studentType.GetMethod("Method2");
method.Invoke(student, new object[] { "Hello", "World" });
// 8. 获取嵌套类型
Type nestedType = studentType.GetNestedType("SmallStudent");
object smallStudent = assembly.CreateInstance(nestedType.FullName);
// 9. 获取类型信息
Console.WriteLine($"类型名称: {studentType.Name}");
Console.WriteLine($"命名空间: {studentType.Namespace}");
Console.WriteLine($"完整名称: {studentType.FullName}");
11. Assembly加载方式对比
反射中加载程序集有三种常用方式,它们的区别如下:
| 方法 | 说明 | 适用场景 |
|---|---|---|
Assembly.Load(assemblyName) |
根据程序集名称加载,会搜索GAC、应用程序目录等 | 加载已引用的程序集 |
Assembly.LoadFrom(path) |
根据文件路径加载,会解析依赖并加载 | 加载外部DLL及其依赖 |
Assembly.LoadFile(path) |
根据文件路径加载,不解析依赖 | 只加载单个DLL文件 |
使用示例:
// 方式1:根据程序集名称加载(需已引用)
Assembly assembly1 = Assembly.Load("ClassLibrary1");
// 方式2:根据路径加载(会解析依赖)
string path1 = @"C:\libs\ClassLibrary1.dll";
Assembly assembly2 = Assembly.LoadFrom(path1);
// 方式3:根据路径加载(不解析依赖)
string path2 = @"C:\libs\ClassLibrary1.dll";
Assembly assembly3 = Assembly.LoadFile(path2);
注意:
LoadFile不会加载依赖项,适合只需要单个DLL的场景LoadFrom会自动加载依赖的DLL,但可能导致同一程序集被加载多次Load适合加载项目中已引用的程序集
12. 性能注意事项
反射的缺点:
- 性能问题:反射是解释操作,远慢于直接代码调用,涉及装箱拆箱操作
- 维护问题:反射绕过了源代码,代码逻辑不够直观,增加维护难度
性能优化建议:
- 缓存Type对象:避免重复获取类型信息
- 减少反射调用次数:将反射结果缓存起来
- 使用泛型约束:在编译时确定类型,减少运行时反射
- 考虑代码生成:对于性能敏感场景,可在编译时生成代码
13. 反射重要API汇总
| API | 返回类型 | 功能 |
|---|---|---|
Type.GetField(name) |
FieldInfo |
获取指定字段 |
Type.GetFields() |
FieldInfo[] |
获取所有字段 |
Type.GetProperty(name) |
PropertyInfo |
获取指定属性 |
Type.GetProperties() |
PropertyInfo[] |
获取所有属性 |
Type.GetMethod(name) |
MethodInfo |
获取指定方法 |
Type.GetMethods() |
MethodInfo[] |
获取所有方法 |
Type.GetConstructor() |
ConstructorInfo |
获取构造函数 |
Type.GetEvent(name) |
EventInfo |
获取事件 |
Type.GetCustomAttribute(type) |
Attribute |
获取特性 |
Type.GetCustomAttributes(type) |
Attribute[] |
获取特性数组 |
Type.GetMembers() |
MemberInfo[] |
获取所有成员 |
Type.GetNestedType(name) |
Type |
获取嵌套类型 |
PropertyInfo.GetValue(obj) |
object |
获取属性值 |
PropertyInfo.SetValue(obj, value) |
void |
设置属性值 |
FieldInfo.GetValue(obj) |
object |
获取字段值 |
FieldInfo.SetValue(obj, value) |
void |
设置字段值 |
MethodInfo.Invoke(obj, params) |
object |
调用方法 |
三、实际应用案例
案例1:通过反射读取外部DLL
场景:项目需要动态加载外部类库,无需提前引用。
实现步骤:
// 1. 加载程序集
string path = Path.Combine(Environment.CurrentDirectory, "../../libs/ClassLibrary1.dll");
Assembly assembly = Assembly.LoadFile(path);
// 2. 创建实例
object instance = assembly.CreateInstance("ClassLibrary1.Class1");
// 3. 获取类型并操作
Type type = instance.GetType();
PropertyInfo prop = type.GetProperty("Name");
string value = (string)prop.GetValue(instance);
案例2:配置文件驱动的依赖注入(WinForms实际应用)
场景:在WinForms应用中,通过配置文件动态加载数据访问层,实现运行时切换数据源。
配置文件(App.config):
<configuration>
<appSettings>
<add key="DAL" value="ClassLibrary1.Class1" />
</appSettings>
</configuration>
WinForms窗体实现:
using System.Configuration;
using System.Reflection;
using System.Windows.Forms;
public partial class Form1 : Form
{
string assemblyFilePath = Path.Combine(Environment.CurrentDirectory, "../../libs/ClassLibrary1.dll");
Type type = null;
object classInstance = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// 加载程序集
Assembly assembly = Assembly.LoadFile(assemblyFilePath);
// 从配置文件读取类型名称
string typeName = ConfigurationManager.AppSettings["DAL"];
// 创建实例
classInstance = assembly.CreateInstance(typeName);
// 获取类型
type = classInstance.GetType();
}
private void btnGetInfo_Click(object sender, EventArgs e)
{
// 获取所有属性
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo prop in properties)
{
if (prop.Name == "Id")
{
lblId.Text = prop.Name;
txtId.Text = prop.GetValue(classInstance).ToString();
}
else if (prop.Name == "Name")
{
lblName.Text = prop.Name;
txtName.Text = prop.GetValue(classInstance).ToString();
}
}
}
private void btnUpdate_Click(object sender, EventArgs e)
{
// 更新属性值
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo prop in properties)
{
if (prop.Name == "Id")
{
prop.SetValue(classInstance, int.Parse(txtId.Text));
}
else if (prop.Name == "Name")
{
prop.SetValue(classInstance, txtName.Text);
}
}
MessageBox.Show("更新成功!");
}
}
优势:无需重新编译,只需修改配置文件即可切换数据访问层实现。
案例3:自定义特性实现版本管理
场景:为类和方法添加版本信息,通过反射读取版本号。
[My("1.0", "Jack", "入口类", "2026-7-1")]
class Program
{
[My("2.0", "Rose", "Test1方法", "2026-7-2")]
public static void Test1() { }
}
// 通过反射获取版本信息
MethodInfo method = typeof(Program).GetMethod("Test1");
MyAttribute attr = (MyAttribute)method.GetCustomAttribute(typeof(MyAttribute));
Console.WriteLine($"版本: {attr.Version}, 作者: {attr.Name}");
四、总结
特性(Attribute)要点
- 特性是类:必须继承
Attribute基类 - 元数据载体:编译后存入程序集元数据,运行时可通过反射读取
- 内置特性:
Conditional、Obsolete、AttributeUsage等 - 自定义特性:通过继承
Attribute并重写构造函数实现 - 控件特性:
Browsable、Category、Description、DefaultValue用于WinForms设计器
反射(Reflection)要点
- 核心类:
Type、Assembly、MethodInfo、PropertyInfo、FieldInfo等 - 获取类型:
typeof()编译时获取,GetType()运行时获取 - 加载程序集:
Assembly.LoadFile()加载外部DLL,Assembly.Load()加载已引用程序集 - 操作成员:通过
GetMethod()、GetProperty()、GetField()等获取并调用 - 访问私有成员:使用
BindingFlags参数组合 - 性能考虑:反射性能较低,建议缓存结果或减少调用次数
特性与反射的关系
- 特性:负责添加元数据
- 反射:负责获取元数据
两者相辅相成,特性为程序元素附加信息,反射在运行时读取这些信息并做出相应决策。
反射优缺点
优点:
| 优点 | 说明 |
|---|---|
| 灵活性高 | 可在运行时动态加载和操作类型,无需提前硬编码 |
| 扩展性强 | 支持插件化架构,动态加载外部模块 |
| 解耦性好 | 降低模块间耦合,提高自适应能力 |
| 元数据访问 | 可读取程序集元数据,实现自动化工具 |
缺点:
| 缺点 | 说明 |
|---|---|
| 性能较差 | 反射是解释操作,涉及装箱拆箱,远慢于直接调用 |
| 代码复杂 | 反射代码比直接代码更复杂,可读性差 |
| 维护困难 | 绕过源代码,增加维护难度 |
| 安全风险 | 可访问私有成员,可能破坏封装性 |
最佳实践
- 特性用于声明式配置:将配置信息附加到代码元素上
- 反射用于运行时发现:在需要动态加载和操作类型时使用
- 结合使用:特性提供元数据,反射读取并利用这些元数据
- 性能优化:缓存反射结果,避免频繁调用
- 安全性:反射可访问私有成员,需注意安全风险
关键区别
| 特性 | 注释 |
|---|---|
| 编译后保留在程序集元数据中 | 编译时被编译器丢弃 |
| 运行时可通过反射读取 | 仅用于代码说明,不影响执行 |
| 可影响程序运行逻辑 | 纯粹的文档说明 |
更多推荐


所有评论(0)