目录
[AttributeUsage 参数表格](#AttributeUsage 参数表格)
[AttributeTargets 常用枚举值](#AttributeTargets 常用枚举值)
引言
在C#开发中,代码不仅仅是实现功能的工具,更是团队协作 与长期维护 的核心载体。如何在代码中高效传递元数据信息( 如作者、版本、调试标记等)?特性(Attribute)为此提供了优雅的解决方案。特性允许开发者通过声明式语法为代码元素(类、方法、属性等)附加额外信息,这些信息既不影响代码逻辑,又能被编译器、框架或反射机制读取,从而实现灵活的代码控制与自动化处理。无论是标记过时方法、记录调用者信息,还是与外部API交互,特性都在简化开发流程中扮演重要角色。本文将从特性基础出发,深入讲解自定义特性的设计与应用,并结合系统内置特性解析其实际场景中的价值。
一、什么是特性
特性就像是给代码元素(类、方法、属性等)贴上的"标签",用来记录一些额外的信息 。比如你买了一本书,书里可能会夹一张便签,写着"这本书是2023年出版的"或者"这本书需要管理员权限才能阅读"。特性就是这样的便签,它们本身不会改变书的(代码的)内容 ,但能告诉其他人(编译器、框架或开发者)关于这本书(代码)的某些重要信息。
元数据存储:特性为代码添加额外的描述信息(如作者、版本、用途等)。
运行时或编译时读取:通过反射可以在程序运行时读取这些信息,或者让编译器根据特性做出特定行为(如生成警告)。
广泛应用:控制序列化、标记过时代码、简化日志记录、调用外部函数等。
特性是一种允许我们向程序的程序集 添加元数据 的语言结构它是用于保存程序结构信息的某种特殊的类
特性提供功能强大的方法以将声明信息与C#代码(类型 方法 属性)相关联
特性与程序实体相关联后 即可在运行时 使用反射查询特性信息
特性的目的是 告诉编译器把程序结构的某组元数据镶嵌到程序集 中
他可以放置在几乎所有的申明中(类 变量 函数等等申明)
说人话:
特性本身是个类我们可以利用特性类来为元数据添加额外信息
比如一个类、成员变量成员方法等等为他们添加更多的额外信息
之后可以通过反射来获取这些信息
二、怎么自定义特性
基本语法
定义特性类 :继承
System.Attribute
,类名以Attribute
结尾(可省略)。构造函数:定义必选参数。
公共属性/字段:定义可选参数。
限制使用范围 :通过
[AttributeUsage]
指定特性可应用的目标。
例如:这是一个先进行特性类的申明,也就是说到这里我们已经有了一个特性类的,接下来就是对于该特性类的使用
cs
// 1. 定义特性类:记录代码作者信息
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Method, // 允许用在类或方法上
AllowMultiple = true, // 允许多次应用
Inherited = false // 不继承到子类
)]
public class AuthorInfoAttribute : Attribute
{
// 必选参数:作者姓名(通过构造函数传入)
public string Name { get; }
// 可选参数:版本号(通过属性设置)
public string Version { get; set; }
// 构造函数:必须传入作者姓名
public AuthorInfoAttribute(string name)
{
Name = name;
}
}
三、怎么使用自定义的特性
书接上文,咱们来看看咋用的:
通过上一篇文章学习到的反射,进行使用,不过一般不会把特性得出来使用,一般就是在你想要添加特性的前面,加上特性标签就可以了
示例:定义、应用并读取自定义特性
(1)定义自定义特性类
cs
using System;
// 定义特性类,约定以Attribute结尾
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Method, // 允许用在类和方法上
AllowMultiple = true, // 允许多次应用
Inherited = false // 不继承到子类
)]
public class MyCustomAttribute : Attribute
{
public string Info { get; }
// 构造函数:必须传入参数
public MyCustomAttribute(string info)
{
Info = info;
}
// 可选方法
public void TestFun()
{
Console.WriteLine("特性中的方法被调用");
}
}
(2) 应用特性到类和方法
cs
// 应用特性到类(允许多个)
[MyCustom("这是Class的注释1")]
[MyCustom("这是Class的注释2")]
public class MyClass
{
// 应用特性到方法
[MyCustom("这是Method的注释")]
public void MyMethod() { }
}
(3)通过反射读取特性信息
cs
using System;
using System.Reflection;
class Program
{
static void Main()
{
// 获取类型信息(三种等效方式)
Type t = typeof(MyClass); // 方式1:直接通过类型
// Type t = new MyClass().GetType(); // 方式2:通过对象实例
// Type t = Type.GetType("命名空间.MyClass"); // 方式3:通过完整类名
// 判断类型是否应用了某个特性
// 参数1:特性类型
// 参数2:是否搜索继承链(属性和事件忽略此参数)
bool isDefined = t.IsDefined(typeof(MyCustomAttribute), false);
Console.WriteLine($"类型是否应用了MyCustom特性:{isDefined}"); // 输出:True
// 获取所有自定义特性(包括其他特性)
object[] allAttributes = t.GetCustomAttributes(true);
Console.WriteLine($"特性数量:{allAttributes.Length}");
// 遍历并处理MyCustom特性
foreach (object attr in allAttributes)
{
if (attr is MyCustomAttribute myAttr)
{
Console.WriteLine($"特性信息:{myAttr.Info}");
myAttr.TestFun(); // 调用特性中的方法
}
}
// 专门获取MyCustom特性(直接过滤)
var customAttributes = t.GetCustomAttributes<MyCustomAttribute>(false);
Console.WriteLine($"找到的MyCustom特性数量:{customAttributes.Count()}");
}
}
结果:

几个方法的说明:
方法 | 作用 |
---|---|
IsDefined(Type, bool) |
快速判断是否应用了某个特性(不实例化特性对象,性能更高) |
GetCustomAttributes(bool) |
获取所有特性实例(true 包含继承链中的特性) |
GetCustomAttributes<T>() |
直接获取指定类型的特性实例(推荐,代码更简洁) |
注意:
AllowMultiple控制 :若特性类未设置
AllowMultiple = true
,重复应用同一特性会编译报错。继承链搜索 :
Inherited = true
时,派生类会继承基类的特性(需配合GetCustomAttributes
的inherit
参数)。性能优化:频繁反射获取特性时,建议缓存结果。
四、限制自定义特性的使用范围
AttributeUsage
参数表格
参数名 | 类型 | 说明 | 默认值 |
---|---|---|---|
ValidOn |
AttributeTargets |
指定特性可以应用的目标(如类、方法、属性等) | 无(必填) |
AllowMultiple |
bool |
是否允许同一目标多次应用同一个特性 | false |
Inherited |
bool |
是否允许派生类继承父类中应用的特性 | true |
AttributeTargets
常用枚举值
枚举值 | 说明 |
---|---|
Assembly |
程序集 |
Class |
类 |
Method |
方法 |
Property |
属性 |
Field |
字段 |
Constructor |
构造函数 |
Parameter |
方法参数 |
通过 [AttributeUsage]
指定:
目标类型 :用
AttributeTargets
枚举(如Class
、Method
)。是否允许多次应用 :
AllowMultiple
。是否被继承 :
Inherited
。
示例:限制特性只能用于方法,且不可继承
cs
using System;
[AttributeUsage(
AttributeTargets.Method, // 只能用于方法
AllowMultiple = false, // 不能重复应用
Inherited = false // 不可继承
)]
public class LogExecutionTimeAttribute : Attribute { }
public class Demo
{
[LogExecutionTime] // 正确:应用于方法
public void Calculate() { }
}
public class SubDemo : Demo
{
// 尝试继承父类方法的特性会失败(Inherited=false)
public void NewMethod() { }
}
// 错误示例
public class InvalidUse
{
[LogExecutionTime] // 错误:不能应用于字段
public int Value;
}
五、系统自带的特性-过时特性
标记代码已过时,编译时生成警告或错误。
用于提示用户 使用的方法等成员已经过时 建议使用新方法
一般加在函数前
语法结构:[Obsolete(string message, bool isError)]
message
:必填,提示信息。
isError
:可选(默认false
),设为true
时触发编译错误。
代码示例:可以看到写代码时他就自动给你画横线了,说明该方法废弃了,主要是以后进行版本更迭的时候可能会使用到:
cs
using System;
public class PaymentService
{
[Obsolete("请使用 NewProcess() 方法", false)] // 警告
public void Process() { }
[Obsolete("此方法已删除", true)] // 错误
public void OldMethod() { }
public void NewProcess() { }
}
class Program
{
static void Main()
{
var service = new PaymentService();
service.Process(); // 编译时警告
// service.OldMethod(); // 编译错误
}
}

六、系统自带的特性-调用者信息特性
自动获取调用者的方法名、文件路径、行号,用于日志或调试。
哪个文件调用
CallerFilePath 特性用于获取调用者的文件名哪一行调用
CallerLineNumber 特性用于获取调用者的行号哪个函数调用
CallerMemberName 特性用于获取调用者的函数名需要应用命名空间 using System.Runtime.CompilerServices;
一般作为函数参数的特性
**语法:void Log(CallerMemberName\] string member = "", \[CallerFilePath\] string file = "", \[CallerLineNumber\] int line = 0 )** 参数必须为**可选参数**并带默认值。 由编译器自动填充值,无需手动传递。 看下面的代码,直接使用就好啦
cs
using System;
using System.Runtime.CompilerServices;
public static class Logger
{
public static void Log(
string message,
[CallerMemberName] string caller = "",
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0
)
{
Console.WriteLine($"消息:{message}");
Console.WriteLine($"调用者:{caller}");
Console.WriteLine($"文件:{file}");
Console.WriteLine($"行号:{line}");
}
}
class Program
{
static void Main()
{
Logger.Log("测试日志"); // 自动填充调用者信息
}
}
七、系统自带的特性-条件编译特性
根据编译符号决定是否编译方法,常用于调试代码。
条件编译特性
Conditional
他会和与处理器指令#define 配合使用
需要引用命名空间 using System.Diagnostics;
主要可以用在一些调试代码上
有时想执行有时不想执行的代码
语法:[Conditional("SYMBOL_NAME")]方法返回值必须为
void
。编译符号需通过项目配置定义(如
DEBUG
)。
可以去实际调试一下看看效果:
Debug 配置:
右键项目 → 属性 → Build → 勾选
Define DEBUG constant
。Release 配置:
切换为 Release 模式 → 确保
Define DEBUG constant
未勾选。

cs
using System;
using System.Diagnostics;
public class DebugHelper
{
[Conditional("DEBUG")]
public static void Log(string message)
{
Console.WriteLine($"[DEBUG] {message}");
}
}
class Program
{
static void Main()
{
DebugHelper.Log("这是一个调试信息");
Console.WriteLine("程序正常执行");
}
}
八、系统自带的特性-外部Dll包函数特性
调用非托管DLL(如Windows API)中的函数。
DllImport 特性用于导入外部Dll包函数
用于标记非.NET(C#)的函数,表明该函数在一个外部的Dll中定义
一般用来调用C或者C++的Dll包写好的方法
需要引用命名空间 using System.Runtime.InteropServices;
使用规则:[DllImport("dll名称", EntryPoint = "函数名", CharSet = CharSet.Auto)]
public static extern 返回类型 方法名(参数);
EntryPoint
:指定DLL中的实际函数名。
CharSet
:控制字符串编码(如CharSet.Unicode
)。
cs
using System;
using System.Runtime.InteropServices;
public class MessageBoxDemo
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(
IntPtr hWnd,
string text,
string caption,
int options
);
}
class Program
{
static void Main()
{
MessageBoxDemo.MessageBox(
IntPtr.Zero,
"Hello, World!",
"提示",
0 // 0表示只有一个OK按钮
);
}
}
看!我们实现了一个框

总结
特性(Attribute) 作为C#中声明式编程 的核心机制,通过为代码元素附加元数据,显著提升了代码的可读性 和可维护性 。自定义特性允许开发者根据需求扩展元数据类型 ,例如通过**AuthorInfoAttribute
** 记录代码作者信息,或通过**LogExecutionTimeAttribute
** 标记性能监控点。借助反射技术,这些特性信息可在运行时被动态解析 ,从而实现自动化文档生成、权限校验或日志记录等功能。
系统内置特性则进一步简化了常见开发场景。例如,Obsolete
特性能够优雅地标记过时代码并引导迁移;CallerMemberName
特性在日志系统中自动捕获调用者上下文,避免硬编码;Conditional
特性通过条件编译灵活控制调试代码的生效范围;而DllImport
特性则无缝桥接非托管代码,扩展了C#的生态兼容性。
特性的核心价值在于其"非侵入性"------在不修改代码逻辑的前提下,通过元数据传递关键信息。这种设计使得代码更易于扩展和协作,尤其是在大型项目或框架开发中,特性能够规范编码标准、简化配置管理,并为工具链(如单元测试框架、序列化库)提供丰富的运行时上下文。掌握特性的设计与应用,是迈向高效、专业化C#开发的重要一步。