CS4L21——特性
CS4L21——特性
本章代码关键字
1 | Attribute // 特性基类,声明特性类时需要继承此类 |
特性
特性是一种允许我们向程序的程序集添加元数据的语言结构,它是用于保存程序结构信息的某种特殊类型的类
特性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联
特性与程序实体关联后,即可在运行时使用反射(Type、MemberInfo 等等)查询特性信息,
特性的目的是告诉编译器把程序结构的某种元数据嵌入程序集中,它可以放置在几乎所有的声明中(类、变量、函数等等声明语句前)
特性的作用包括:
-
标记和描述代码元素特性可以用来为代码提供额外信息。
-
控制编译器行为特性可以用于影响编译器的某些行为,
例如:
[Obsolete]
:标记某个方法或类已被弃用,调用时会产生警告或错误。[Conditional]
:决定某段代码是否编译。
简单来说就是,特性本质上就是个类,我们可以利用特性类为 元数据 添加额外信息
比如一个类、成员变量、成员方法等等为他们添加更多的额外信息,之后可以通过反射来获取这些额外信息
声明自定义特性
声明特性类需要继承特性基类 Attribute
,同时,特性类的命名需要添加后缀 Attribute
1 | class CustomAttribute : Attribute |
特性的使用
特性的添加
基本语法:在类、变量、函数声明语句前加上:[特性名(参数列表)]
,表示它们具有该特性信息,它的本质就是在调用特性类的构造函数
添加特性时,特性类的后缀 Attribute
在使用特性时可以省略掉
1 | [ ] |
这样,TestClass
这个类型就被添加了特性,之后可以通过其对应的 Type
对象获取这些类型
对于特性类的公开变量,可以在添加特性时直接对特性类的某个公开变量赋值,注意,它和构造函数传参不一样!
在参数列表左边赋值即可,可以赋值多个变量,即: [特性名(参数列表, 公开变量 = 值, ...)]
1 | [ ] |
判断某个类型是否使用了某个特性
type.IsDefined()
可以判断某个类型是否使用了某个特性
- 参数一:特性的类型
Type
- 参数二:是否要顺着继承链向上搜索父类是否使用参数一传入的特性(属性和事件忽略此参数)
- 返回值:是否使用特性
1 | TestClass obj = new TestClass(); |
输出:
1 | 该类型应用了Custom特性 |
获取某个类型使用的所有特性
type.GetCustomAttributes()
可以获取此类型所有特性对象
- 参数一:是否要顺着继承链向上搜索父类使用的特性(属性和事件忽略此参数)
- 返回值:使用的所有特性对象数组
默认返回的是 object[]
,可以转成对应的特性类型对象,并执行特性中的成员变量和方法
1 | TestClass obj = new TestClass(); |
输出:
1 | 这是 MyClass 类 |
获取某个类型使用的某个特性
type.GetCustomAttribute()
可以获取此类型特定的特性对象,它有泛型参数重载和 Type
参数重载
- 如果使用泛型函数,则直接在泛型参数内填入特性名即可,返回值也是对应的特性
- 如果使用
Type
参数函数,需要传入特性对应的Type
,返回值是object
- 泛型参数重载和
Type
参数重载都有一个bool
参数,即是否要顺着继承链向上搜索父类使用的特性(属性和事件忽略此参数)
警告!如果这个特性被重复添加到这个类型上,那么使用此函数获取特性将会报错!
1 | TestClass obj = new TestClass(); |
输出:
1 | 这是 TestClass 类 |
获取某个成员使用的特性
如果要获取类型中某个成员使用的特性,需要通过 Type
来获取 MemberInfo(也可以是 FieldInfo、MethodInfo 等等)
然后通过 MemberInfo
来调用 GetCustomAttribute()
或者 GetCustomAttributes()
,即可得到某个成员使用的特性
参数和 type.GetCustomAttribute() 与 type.GetCustomAttributes() 一致
1 | TestClass obj = new TestClass(); |
输出:
1 | 这是 TestClass.MyClass 方法 |
限制自定义特性的使用范围
通过为特性类添加 [AttributeUsage()]
特性,可以限制其使用范围
-
参数一:
validOn
—— 特性能够添加在哪些地方(注意这里用位或|
来表示两个条件都可以)此参数需要传入
AttributeTargets
枚举类型参数,可以传入多个,所有位或|
连接1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20[ ]
public enum AttributeTargets
{
Assembly = 1, // 特性可以添加在程序集前
Module = 2, // 特性可以添加在模块前
Class = 4, // 特性可以添加在类声明前
Struct = 8, // 特性可以添加在结构体声明前
Enum = 16, // 特性可以添加在枚举声明前
Constructor = 32, // 特性可以添加在构造函数声明前
Method = 64, // 特性可以添加在方法声明前
Property = 128, // 特性可以添加在属性声明前
Field = 256, // 特性可以添加在成员变量声明前
Event = 512, // 特性可以添加在事件声明前
Interface = 1024, // 特性可以添加在接口声明前
Parameter = 2048, // 特性可以添加在方法的参数前
Delegate = 4096, // 特性可以添加在委托前
ReturnValue = 8192, // 特性可以添加在方法的返回值前
GenericParameter = 16384, // 特性可以添加在泛型参数前
All = 32767 // 特性可以添加在所有的目标前
}1
2
3
4
5
6
7
8[ ]
class CustomAttribute : Attribute {}
[ ]
class TestClass {}
[// error: 特性“Custom”对此声明类型无效。它仅对“类”声明有效。 ]
struct TestStruct {}
此外,还有两个公共变量可以赋值:
-
变量一:
AllowMultiple
—— 是否允许多个特性实例用在同一个目标上如果此变量为
true
,则此特性可以一次在同一目标上重复添加多个1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[ ]
[ ]
class TestClass {}
[ ]
class CustomAttribute : Attribute
{
// 特性中的成员一般根据需求来写
public string info;
public int number;
public CustomAttribute(string info)
{
this.info = info;
}
} -
变量二:
Inherited
—— 特性是否能被派生类和重写成员继承如果此变量为
true
,父类添加的特性,在子类同样有效
系统自带特性
过时特性
过时特性 [Obsolete]
,用于提示用户使用的方法等成员已经过时,建议使用新方法,一般加在函数前的特性
- 参数一:
message
,调用过时内容时提示内容 - 参数二:
error
,调用目标时是否报错(默认是警告)
1 | [ ] |
1 | OldClass old = new OldClass(); // warning: “OldClass”已过时 |
调用者信息特性
以下特性只能为参数使用,且使用调用者信息特性的参数必须初始化为默认值!调用者信息特性会自动将对应参数的默认值改为相应的数据
- 得到哪个文件调用需要对参数添加
[CallerFilePath]
特性 - 得到哪一行调用需要对参数添加
[CallerLineNumber]
特性 - 得到哪个函数调用对参数添加
[CallerMemberName]
特性
需要 using System.Runtime.CompilerServices;
,以上的特性一般作为函数参数的特性
1 | using System.Runtime.CompilerServices; |
输出:
1 | 说话内容:测试调用者信息特性 |
条件编译特性
条件编译特性 [Conditional]
,它会和预处理指令 #define 配合使用,
此特性需要传入 #define
定义的符号,如果此符号未定义,则该函数不调用
需要引用 using System.Diagnostics;
,主要可以用在一些调试代码上,有时想执行有时不想执行的代码
1 |
|
输出(如果 Debug
未定义,则不会输出此内容):
1 | 调试函数执行 |
外部Dll包特性
[DllImport]
用来标记非 .NET(C#) 的函数,表明该函数在一个外部的 dll
文件中定义,此特性需要传入 dll
文件所在路径
一般用来调用 C 和 C++ 的 dll
包写好的方法,需要引用 using System.Runtime.InteropServices;
固定写法如下(假设同级目录下存在一个 Test.dll
文件):
1 | class Program |