目录
什么是特性
Serializable
DllImport
Obsolete
Conditional
Attribute类
自定义特性
AttributeUsage的使用例子
特性非常常见,官方解释为:
特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。
我们直接通过实例来理解究竟什么是特性。
什么是特性
Serializable
首先,当一个类需要支持序列化时,我们总能看到类名上方有如下的结构:
[Serializable]
class SampleClass
{
// ...
}
[Serializable]
就是用来标识当前类是否可序列化的。
DllImport
用来标记非.NET的函数,表明该方法在一个外部的DLL中定义。
[DllImport("User32.dll")]
public static extern int MessageBox(int hParent, string Message, string Caption, int Type);
这段代码中 [DllImport]表明了MessageBox是User32.DLL中的函数,这样我们就可以像内部方法一样调用这个函数。
Obsolete
当我们的程序在迭代过程中出现了一些打算弃用的方法,如果直接删除的话,势必会影响到版本的兼容性。因此需要添加一个标识,让开发者注意到这个方法已经弃用,在未来的版本中可能会被删除。这就需要用到[Obsolete]
特性:
[Obsolete("这是需要提示的内容",true)]
public class Test
{
}
当我们的程序在迭代过程中出现了一些打算弃用的方法,如果直接删除的话,势必会影响到版本的兼容性。因此需要添加一个标识,让开发者注意到这个方法已经弃用,在未来的版本中可能会被删除。这就需要用到[Obsolete]
特性:
这样在调用被[Obsolete(string,bool)]
标记的方法,当bool类型为true时,编译器就会进行提示并报错:
Conditional
在开发环境下,我们经常需要编写一些调试方法,而在打包时又需要去除这些代码。挨个删除显然是不现实的,此时就需要用到[Conditional]
特性。它需要在参数中传递一个字符串,编译器会根据这个字符串寻找同名的宏。如果这个宏定义了,该特性修饰的方法就会启用,反之则禁用。
#define IsShowMessage
// ...
[Conditional("IsShowMessage")]
public static void Test2()
{
Console.WriteLine("调试信息。。。。");
}
// ...
因此在调试过程中,先用[Conditional]
特性标记调试方法,调试完毕将#define IsShowMessage注释即可一键禁用标记过的调试方法。
通过上面的几个示例,我们再来理解特性的定义:
- 将应用了特性的程序结构叫做目标
- 设计用来获取和使用元数据的程序(对象浏览器)叫做特性的消费者
- NET预定了很多特性,我们也可以声明自定义特性
重要的一点就是Attribute就是一个类,Attribute类是在编译的时候被实例化的,而不是像通常的类那样在运行时候才实例化。
我们对编写的类、方法、属性,方法的参数,方法的返回值等通过特性进行标记,编译器就会根据特性产生元数据,再将这些元数据放入程序集中。消费者就可以获取到这些元数据,并进行使用。
Attribute类
除了.NET提供的那些Attribute派生类之外,我们可以自定义我们自己的Attribute,所有自定义的Attribute必须从Attribute类派生。现在我们来看一下Attribute 类的细节:
三个静态方法:
- static Attribute GetCustomAttribute():这个方法有8种重载的版本,它被用来取出施加在类成员上指定类型的Attribute。
- static Attribute[] GetCustomAttributes(): 这个方法有16种重载版本,用来取出施加在类成员上指定类型的Attribute数组。
- static bool IsDefined():由八种重载版本,看是否指定类型的定制attribute被施加到类的成员上面。
实例方法:
- bool IsDefaultAttribute(): 如果Attribute的值是默认的值,那么返回true。
- bool Match():表明这个Attribute实例是否等于一个指定的对象。
公共属性:
- TypeId: 得到一个唯一的标识,这个标识被用来区分同一个Attribute的不同实例。
自定义特性
首先,特性是一个类。在自定义特性时,我们自定义的特性类名中必须包含Attribute,同时继承(直接继承或者间接继承)Attribute类。然后需要在该类上通过AttributeUsage特性指明自定义特性的应用范围。比如我们定义一个TestAttribute特性,继承自Attribute类,且要求定义的类只能标记到其他类上面。
[AttributeUsage(AttributeTargets.Class)]
public class TestAttribute : Attribute
{
public string Name { get; set; }
public int Age { get; set; }
public string Say { get; set; }
public TestAttribute(string name,int age,string say)
{
this.Name = name;
this.Age = age;
this.Say = say;
}
}
AttributeTargets.Class表示,自定义特性TestAttribute只能标记到类上面。然后我们在定义一个类UseTest,将自定义特性标记到该类上,使用自定义特性时不需要加Attribute后缀。通过反射可以获取到指定的特性:
[Test("小明",30," say hello")]
public class UseTest
{
public static void AttributeTestMain()
{
//获取UseTest类型
var type = typeof(UseTest);
//判断该类型是否定义了TestAttrubte特性
if (type.IsDefined(typeof(TestAttribute), false))
{
//通过反射获取自定义特性属性
var customAttributes = type.GetCustomAttributes(false);
if (customAttributes.Length > 0)
{
var classinfo = Array.Find(customAttributes, x => x is TestAttribute) as TestAttribute;
Console.WriteLine($"名字:{classinfo?.Name}\n年龄:{classinfo.Age}\n说:{classinfo.Say}");
}
}
}
}
在调用UseTest类中的静态方法AttributeTestMain()后,输出信息为:
非常有意思的是,AttributeUsage本身也是一个Attribute,这是专门施加在Attribute类的Attribute. AttributeUsage自然也是从Attribute派生。AttributeUsage除了继承Attribute 的方法和属性之外,还定义了以下三个属性:
- AllowMultiple: 读取或者设置这个属性,表示是否可以对一个程序元素施加多个Attribute 。
- Inherited:读取或者设置这个属性,表示是否施加的Attribute 可以被派生类继承或者重载。
- ValidOn: 读取或者设置这个属性,指明Attribute 可以被施加的元素的类型。
AttributeUsage的使用例子
using System;
namespace AttTargsCS
{
// 该Attribute只对类有效.
[AttributeUsage(AttributeTargets.Class)]
public class ClassTargetAttribute : Attribute
{
}
// 该Attribute只对方法有效.
[AttributeUsage(AttributeTargets.Method)]
public class MethodTargetAttribute : Attribute
{
}
// 该Attribute只对构造器有效。
[AttributeUsage(AttributeTargets.Constructor)]
public class ConstructorTargetAttribute : Attribute
{
}
// 该Attribute只对字段有效.
[AttributeUsage(AttributeTargets.Field)]
public class FieldTargetAttribute : Attribute
{
}
// 该Attribute对类或者方法有效(组合).
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
public class ClassMethodTargetAttribute : Attribute
{
}
// 该Attribute对所有的元素有效.
[AttributeUsage(AttributeTargets.All)]
public class AllTargetsAttribute : Attribute
{
}
//上面定义的Attribute施加到程序元素上的用法
[ClassTarget] //施加到类
[ClassMethodTarget]//施加到类
[AllTargets] //施加到类
public class TestClassAttribute
{
[ConstructorTarget] //施加到构造器
[AllTargets] //施加到构造器
TestClassAttribute()
{
}
[MethodTarget] //施加到方法
[ClassMethodTarget] //施加到方法
[AllTargets] //施加到方法
public void Method1()
{
}
[FieldTarget] //施加到字段
[AllTargets] //施加到字段
public int myInt;
static void Main(string[] args)
{
}
}
}