什么是特性
来看一段微软官方文档的描述:
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用「反射」这项技术查询特性。
看完之后好像明白了什么,但又没完全明白。其实特性非常常见,我们直接通过实例来理解究竟什么是特性:
Serializable
首先,当一个类需要支持序列化时,我们总能看到类名上方有如下的结构:
[Serializable]
class SampleClass
{
// ...
}
[Serializable]
就是用来标识当前类是否可序列化的。
Obsolete
当我们的程序在迭代过程中出现了一些打算弃用的方法,如果直接删除的话,势必会影响到版本的兼容性。因此需要添加一个标识,让开发者注意到这个方法已经弃用,在未来的版本中可能会被删除。这就需要用到[Obsolete]
特性:
class SampleClass
{
// ...
[Obsolete]
public static void Test()
{
}
}
这样在调用被[Obsolete]
标记的方法时,编译器就会进行警告:
此外,该特性还支持添加参数:
// 参数一:提示信息 参数二:是否error
[Obsolete("该方法已被弃用",true)]
public static void Test()
{
}
Conditional
在开发环境下,我们经常需要编写一些调试方法,而在打包时又需要去除这些代码。挨个删除显然是不现实的,此时就需要用到[Conditional]
特性。它需要在参数中传递一个字符串,编译器会根据这个字符串寻找同名的宏。如果这个宏定义了,该特性修饰的方法就会启用,反之则禁用。
#define IsShowMessage
// ...
[Conditional("IsShowMessage")]
public static void Test2()
{
Console.WriteLine("调试信息。。。。");
}
// ...
调用者信息特性
利用Caller特性,可以将调用者的某些信息(如行号、文件路径、成员名称等)作为参数传入。不过需要注意用该特性修饰的参数需要具有默认值。
// ...
public static void Test3(String message,[CallerLineNumber] int lineNum=0
,[CallerFilePath] string path="",[CallerMemberName] string name="")
{
Console.WriteLine(message);
Console.WriteLine(lineNum);
Console.WriteLine(path);
Console.WriteLine(name);
}
// ...
public static void AttributesPracticeMain()
{
SampleClass.Test3("调用Test3");
}
输出结果如下:
DebuggerStepThrough
这个特性看名字就能明白个大概,就是被这个特性修饰的方法,在调试模式下不会进入。
[DebuggerStepThrough]
public static void Test4()
{
Console.WriteLine("调试信息。。。。");
}
什么是特性
看完了上面的几个示例,我们再来理解特性的定义:
特性(attribute)是一种允许我们向程序的程序集添加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。
- 将应用了特性的程序结构叫做目标
- 设计用来获取和使用元数据的程序(对象浏览器)叫做特性的消费者
- NET预定了很多特性,我们也可以声明自定义特性
也就是说,我们在编写的类、方法、参数中添加了特性,编译器就会根据特性产生元数据,再将这些元数据放入程序集中。消费者就可以获取到这些元数据,并进行使用。
自定义特性
如果还是不明白特性究竟是什么东西,我们就来自己实现一个特性,看看它究竟是如何运作的。
首先特性本质上就是一个类,我们自定义的特性类名中必须包含Attribute,同时继承Attribute类。然后需要在该类上通过AttributeUsage特性指明自定义特性的应用范围:
[AttributeUsage(AttributeTargets.Class)]
public sealed class ClassInfoAttribute:Attribute
{
public string Author { get; }
public string CreateDate { get; }
public string Description { get; }
public ClassInfoAttribute(string author, string createDate, string description)
{
Author = author;
CreateDate = createDate;
Description = description;
}
}
然后就可以直接使用这个特性了。不过需要注意的一点是,使用自定义特性时不需要加Attribute后缀。通过反射可以获取到指定的特性:
[ClassInfo("张三","2022/08/13","这是一个自定义特性测试类")]
class AttributeTest
{
public static void AttributeTestMain()
{
var type = typeof(AttributeTest);
// 是否定义了ClassInfoAttribute特性
if (type.IsDefined(typeof(ClassInfoAttribute), false))
{
var customAttributes = type.GetCustomAttributes(false);
if (customAttributes is {Length: > 0})
{
var classInfo = Array.Find(customAttributes, item => item is ClassInfoAttribute) as ClassInfoAttribute;
Console.WriteLine($"Author: {classInfo?.Author} Date: {classInfo?.CreateDate} Des: {classInfo?.Description}");
}
}
}
}
输出信息如下:
参考文献:
[1]马克·米凯利斯.C#8.0本质论[M].机械工业出版社.