前言:

特性就像是给代码贴上的**"标签"或 "注释"。但它不仅仅是给程序员看的注释,它还是给 编译器或程序本身**看的。通过这些标签,你可以告诉程序:"这个方法已经过时了"或者"这个类在保存到数据库时叫另一个名字
一什么是特性
在C#中,特性是一种元数据,元数据是关于数据的数据。
**语法特征:**特性通常写在类,方法,属性等代码元素的上方,用方括号[ ]包围
**作用:**它们不直接改变程序的逻辑,但可以在编译时或运行时被读取,从而影响程序的行为
二为什么要用特性
想象你在网购,每一个商品包裹上都有一个快递单(特性):
-
易碎标识: 快递员看到后会轻轻放下(编译器/运行时处理逻辑)。
-
重量信息: 分拣机根据它分配路线(工具处理逻辑)。
在代码里,我们可以用特性来标记:
-
这个方法是用来测试的。
-
这个变量必须是 1 到 100 之间的数字。
-
这个类可以被转换成 XML 格式。
三常见的内置特性
1.[Obsolete]过时
提示开发者,这个方法已经旧了,建议使用新的
cs
[Obsolete("请使用 NewMethod,旧方法将在下个版本移除")]
public void OldMethod() {
// ...
}
如果你在代码里调用它,编译器会显示一条警告
用法1:只提示警告
cs
[Obsolete]
public void OldMethod() { }
效果:
- 调用这个方法时,VS 会画绿色波浪线
- 只是警告,代码照样编译、照样运行
用法2:带提示文字的警告
cs
[Obsolete("这个方法旧了,请用 NewMethod 代替")]
public void OldMethod() { }
效果:
- 警告 + 你写的提示文字
- 还是能编译、能运行
用法3:直接报错,不让编译
cs
[Obsolete("已废弃,禁止使用", true)]
第二个参数写 true:
- 变成错误,红色波浪线
- 无法编译通过,直接禁止使用
2.[Serializable](可序列化)(老板二进制序列化方式,现在不用了)
1.什么是序列化
序列化的本质,就是把内存里的对象(比如C#的类实例),转换成一串可以存储/传输的格式,之后还能完整还原成原来的对象
- 就像把一个复杂的家具,拆解打包成零件(序列化),运到新家再组装回去(反序列化)。
- 不同的「打包规则」,就是不同的序列化方案
2. [Serializable] 到底给谁用?
告诉编译器,这个类的内容可以被转换成二进制流存到硬盘或通过网络传输。

它是给 .NET 原生二进制序列化 用的:
BinaryFormatterSoapFormatter- 旧版 Remoting
- 旧版 AppDomain 跨域传对象
- 一些老的缓存框架
这些东西要把对象打成二进制字节流 ,就要求类必须标记 [Serializable],否则直接报错。
3.[Conditional]
总结
#define DEBUG= 开关(预处理宏)[Conditional("DEBUG")]= 听开关的标签- 开关开 → 方法调用保留
- 开关关 → 方法调用被删除
1.宏是什么
宏 = 代码里的 "代号",在编译前会被替换成具体内容。
它不是一个真实的变量,也不是一个真实的函数,它只是文本替换
2.什么是预处理宏

核心作用:
给方法加上这个特性,只有在编译时定义了指定的「预处理宏」,这个方法的调用才会被编译器保留;否则调用会被直接删除,方法本身也不会被编译进最终程序。
最经典的用法:Debug 日志 只在调试模式(DEBUG宏)下输出日志,Release 模式下自动消失,完全不影响正式包的性能和体积。
3.如何使用
#define DEBUG就是一个开关
这个开关就是预处理宏,存在就是打开,不存在就是关闭

1.[Conditional(DEBUG)]是什么

4.调用者信息
简单总结
特性普通用法:给类,属性,方法贴标签
补充:特性的特殊用法
给方法的参数贴标签,告诉编译器【这个参数我要特殊处理】
这三个特性的本质 :编译器指令

必须满足的两个语法要求
- 必须是方法的「可选参数」 :必须给默认值(
member = ""、file = ""、line = 0)- 原因:调用时你不填这个参数,编译器才能用默认值的位置,把自动生成的值填进去
- 必须放在参数列表的「最后」 :可选参数必须在必填参数(
string message)后面- 原因:C# 语法规定「可选参数必须在必填参数之后」,否则调用时会报错

1.是什么
编辑器自动把[ 哪个文件,哪一行,哪个方法调用了我] 填到参数里,不用自己写反射,堆栈查询
2.以前写日志方法
cs
// 老式:手动传参数,麻烦
void Log(string message, string file, int line, string member) {
Console.WriteLine($"{file}:{line} [{member}] {message}");
}
// 调用时要手写
Log("出错了", "Main.cs", 42, "ButtonClick");
3.C# 5.0 :三个特性(自动填充)
用三个特性,编译器编译的时候自动填值
-
CallerMemberName\]:调用方法名
-
CalllerLineNumber\]:代码行号
cs
using System.Runtime.CompilerServices;
void Log(
string message,
[CallerMemberName] string member = "",
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0)
{
Console.WriteLine($"{file}:{line} [{member}] {message}");
}
// 调用时:什么都不用传!
void ButtonClick() {
Log("开始加载数据");
}

黑魔法本质 :不是运行时查堆栈,是编译时直接把文件名、行号、方法名硬编码到调用处 ------ 零性能开销、百分百准确。
四自定义特性
1.什么是自定义特性
- 特性 = 给类 / 方法 / 属性贴个标签
- 自定义特性 = 你自己写一个标签
- 本质就是一个类 ,必须继承自
Attribute
2.最简单的自定义特性
这个类的作用就是用来装数据 的。当你把 [MyTag(Note = "测试", Level = 1)] 贴在某个方法或类上时,其实就是创建了这个类的一个实例 ,并把 Note 和 Level 的值赋给了这个实例的属性。
cs
// 1. 类名必须以 Attribute 结尾
public class MyTagAttribute : Attribute
{
// 2. 可以放一些字段/属性
public string Note { get; set; }
public int Level { get; set; }
}
特性不负责执行逻辑,特性只负责 "存数据",反射负责 "读数据 + 干活"。
使用时可以省略 Attribute,直接写:
cs
[MyTag(Note = "测试标签", Level = 1)]
public class TestClass
{
}
3.控制特性贴在哪里
用[AttributeUsage]限制这个标签贴在哪里
cs
[AttributeUsage(
AttributeTargets.Class | // 能贴类
AttributeTargets.Method | // 能贴方法
AttributeTargets.Property, // 能贴属性
AllowMultiple = true, // 允许贴多次
Inherited = true // 子类继承标签
)]
public class MyTagAttribute : Attribute
{
}
4.特性怎么用
特性自己不会干活 ,必须反射读取它,然后做逻辑
示例
cs
// 贴标签
[MyTag(Note = "旧方法", Level = 2)]
public void Test() { }
读取
cs
// 获取方法上的特性
var method = typeof(类名).GetMethod("Test");
var attr = method.GetCustomAttribute<MyTagAttribute>();
if (attr != null)
{
Console.WriteLine(attr.Note);
Console.WriteLine(attr.Level);
}
五特性的语法结构
特性本质上是一个继承自 System.Attribute 的类。它的使用语法如下:
-
位置: 紧贴在目标代码上方。
-
参数: 有些特性需要传入参数(就像构造函数一样)。
-
后缀: 所有的特性类名都以
Attribute结尾,但在使用时可以省略(如[ObsoleteAttribute]简写为[Obsolete])。
六如何自定义特性
当你发现内置特性不够用时,可以自己写一个。
-
定义: 创建一个类,继承
Attribute。 -
限制: 使用
[AttributeUsage]限制该特性可以贴在什么地方(只能贴在类上?还是只能贴在方法上?)。
cs
// 1. 定义特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DeveloperInfoAttribute : Attribute {
public string Name { get; set; }
public DeveloperInfoAttribute(string name) {
this.Name = name;
}
}
// 2. 使用特性
[DeveloperInfo("小明")]
public class MyProgram { }
七特性是如何生效的
这是一个关键点:特性贴上去后,如果不去读取它,它就是一张死纸。 在 C# 中,我们使用一种叫 反射 (Reflection) 的技术,在程序运行的时候去"扫描"代码上的标签并做出反应。
-
编译期: 特性被编译进 DLL/EXE 文件的元数据中。
-
运行期: 程序通过反射找到这些标签,根据标签内容执行不同的代码。