C# 特性(Attribute)
Attribute是什么
Attribute是一种可由用户自有定义的修饰符(Modifier),可以用来修饰各种需要被修饰的目标。我们可以对类、以及C#程序集中的成员进行进一步的描述。
简单地说,Attribute就是一种“附着物”——就像牡蛎吸附在船底或礁石上一样。 这些附着物的作用是为它们的附着体追加上一些额外的信息(这些信息保存在附着物的体内)——比如“这个类是我写的”或者“这个函数以前出过问题”等等
Attribute与注释的区别
注释是对程序源代码的一种说明,主要目的是给人看的,在程序被编译的时候会被编译器所丢弃,因此,它丝毫不会影响到程序的执行。
Attribute是程序代码的一部分,它不但不会被编译器丢弃,而且还会被编译器编译进程序集(Assembly)的元数据(Metadata)里。在程序运行的时候,随时可以从元数据(元数据:.NET中元数据是指程序集中的命名空间、类、方法、属性等信息,这些信息是可以通过Reflection读取出来的。)中提取提取出这些附加信息,并以之决策程序的运行。
示例
比如我们写一个关于人的类Person,该类可以对人的属性以及某些行为(方法)进行描述。那么如果我们要对人类进行进一步描述呢?比如人这个类是属于动物的灵长类动物,有人会说我们可以为这个Person类去写一个灵长动物类的父类,再用人类去继承这个类去解决。但是我们要求的是仅仅是描述性的,就是对这个人类进行进一步的描述,而在实际操作中不需要去操作。这样的情况我们就可以用特性的概念去解决,特性简而言之就是程序集的特定程序元素所具有的另外的性质。
//先定义一个人类Person类
class Person
{
//人的姓名储存字段和属性
private string name;
public string Name
{
set { name = value; }
get { return name; }
}
//人的年龄储存字段和属性
private int age;
public int Age
{
set { age = value; }
get { return age; }
}
//人的性别储存字段和属性
private char sex;
public char Sex
{
set { sex = value; }
get { return sex; }
}
//人的打招呼方法
public void SayHello()
{
Console.WriteLine($"大家好,我叫{this.Name},我今年{this.Age}岁了,我的性别是{this.Sex}");
}
}
//定义动物的特性类AnimalAttribute类继承于Attribute(特性)
class AnimalAttribute : Attribute
{
//字段和属性描述是否是灵长类
private bool isPrimate;
public bool IsPrimate
{
set { isPrimate = value; }
get { return isPrimate; }
}
}
对人类进行动物类描述。即在人类的定义前面加:
[Animal(IsPrimate = true)] //为人类加特性,指定人类是灵长类。
下面我们就可以通过代码来获得人类的特性:
//声明特性对象,并通过Attribute类的静态方法GetCustomAttribute()获得人类的在动物类的特性,并赋值给特性对象
Attribute att1 = Attribute.GetCustomAttribute(typeof(Person), typeof(AnimalAttribute));
//将特性对象转化为动物特性对象
AnimalAttribute animalAtt = att1 as AnimalAttribute;
//检查转化是否成功如果成功则打印这个特性对象的是否是灵长类的属性。
if (animalAtt != null)
{
Console.WriteLine("人类是否是灵长类:{0}", animalAtt.IsPrimate);
}
Console.ReadKey();
注意:如果一个类有的某个特性类描述多次,则要用GetCustomAttributes()方法。
Attribute的使用方法:(四种方式完全等价)
//长记法
[ConditionalAttribute("Li")]
[ConditionalAttribute("NoBug")]
public static void Func()
{Console.WriteLine("Created by Li, NoBug"); }
//短记法
[Conditional("Li")]
[Conditional("NoBug")]
public static void Func()
{Console.WriteLine("Created by Li, NoBug"); }
//换序
[Conditional("NoBug")]
[Conditional("Li")]
public static void Func()
{Console.WriteLine("Created by Li, NoBug"); }
//单括号叠加
[Conditional("NoBug"),Conditional("Li")]
public static void Func()
{Console.WriteLine("Created by Li, NoBug"); }
预定义的特性
obsolete类用来指定该类的成员已经过时,在程序使用这个成员的时候会给出提示。
obsolete类有三种重载
public ObsoleteAttribute()
public ObsoleteAttribute(string message) 参数说明: message:描述了可选的变通方法文本字符串。
public ObsoleteAttribute(string message, bool error) 参数说明:message:描述了可选的变通方法文本字符串。 error:true 如果使用过时的元素将生成编译器错误;false 如果使用它将生成编译器警告。
比如如果我们的Person类的SayHello()方法现在不能用了,不允许程序员使用这个方法,那么我们就可以在Person类的SayHello方法前面加[Obsolete("该方法已经过期,请找最新的方法", true)]
这样当我们在主程序中用到Person类的SayHello方法的时候程序就会报错。当然,如果第二个参数设置为false时,在调用该成员的时候不会报错,但是会报出警告。
Attribute作为编译器的指令
在C#中存在着一定数量的编译器指令,如:#define DEBUG, #undefine DEBUG, #if等。这些指令专属于C#,而且在数量上是固定的。而Attribute用作编译器指令则不受数量限制。比如下面的三个Attribute:
Conditional:起条件编译的作用,只有满足条件,才允许编译器对它的代码进行编译。一般在程序调试的时候使用。
DllImport:用来标记非.NET的函数,表明该方法在一个外部的DLL中定义。
Obsolete:这个属性用来标记当前的方法已经被废弃,不再使用了。
下面的代码演示了上述三个属性的使用:
#define debug //定义条件
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport("User32.dll")]
public static extern int MessageBox(int hParent, string Message, string Caption, int Type);
[Conditional("debug")]
private static void DisplayRunningMessage()
{
Console.WriteLine($"开始运行Main子程序。当前时间是{DateTime.Now}");
}
[Conditional("debug")]
[Obsolete]
private static void DisplayDebugMessage()
{
Console.WriteLine("开始Main子程序");
}
static void Main(string[] args)
{
DisplayRunningMessage();
DisplayDebugMessage();
MessageBox(0, "Hello", "Message", 0);
Console.ReadKey();
}
}
}
运行结果:
解析:
如果在一个程序元素前面声明一个Attribute,那么就表示这个Attribute被施加到该元素上。
上面的代码,[DllImport]施加到MessageBox函数上, [Conditional]施加到DisplayRuntimeMessage方法和DisplayDebugMessage方法,[Obsolete]施加到DisplayDebugMessage方法上。DllImport Attribute表明了MessageBox是User32.DLL中的函数,这样我们就可以像内部方法一样调用这个函数。
重要的一点就是Attribute就是一个类,所以DllImport也是一个类,Attribute类是在编译的时候被实例化的,而不是像通常的类那样在运行时候才实例化。Attribute实例化的时候根据该Attribute类的设计可以带参数,也可以不带参数,比如DllImport就带有"User32.dll"的参数。Conditional对满足参数的定义条件的代码进行编译,如果没有定义debug,那么该方法将不被编译。Obsolete表明了DispalyDebugMessage方法已经过时了,它有一个更好的方法来代替它,当我们的程序调用一个声明了Obsolete的方法时,那么编译器会给出信息。
Attribute类
除了.NET提供的那些Attribute派生类之外,我们可以自定义我们自己的Attribute,所有自定义的Attribute必须从Attribute类派生。现在我们来看一下Attribute 类的细节:
protected 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并不需要特别的知识,其实就和编写一个类差不多。自定义的Attribute必须直接或者间接地从Attribute这个类派生,如:
public MyCustomAttribute : Attribute { ... }
这里需要指出的是Attribute的命名规范,也就是你的Attribute的类名+"Attribute",当你的Attribute施加到一个程序的元素上的时候,编译器先查找你的Attribute的定义,如果没有找到,那么它就会查找“Attribute名称"+Attribute的定义。如果都没有找到,那么编译器就报错。
自定义或控制特性的使用
对于一个自定义的Attribute,可以通过AttributeUsage的Attribute来限定你的Attribute所施加的元素的类型。代码形式如下:
[AttriubteUsage(参数设置)] public 自定义Attribute : Attribute { ... }
AttributeUsage本身也是一个Attribute,这是专门施加在Attribute类的Attribute. AttributeUsage自然也是从Attribute派生,它有一个带参数的构造器,这个参数是AttributeTargets的枚举类型。下面是AttributeTargets 的定义:
//
// 摘要:
// 指定可应用属性的应用程序元素。
[ComVisible(true)]
[Flags]
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
}
作为参数的AttributeTarges的值允许通过“或”操作来进行多个值得组合,如果你没有指定参数,那么默认参数就是All 。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
AttributeUsage除了继承Attribute 的方法和属性之外,还定义了以下三个属性:
AllowMultiple:读取或者设置这个属性,表示是否可以对一个程序元素施加多个Attribute 。
Inherited:读取或者设置这个属性,表示是否施加的Attribute 可以被派生类继承或者重载。
ValidOn: 读取或者设置这个属性,指明Attribute 可以被施加的元素的类型。
示例:在Help特性前放置AttributeUsage特性以期待在它的帮助下控制Help特性的使用
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)]
public class HelpAttribute : Attribute
{
public HelpAttribute(string Description_in)
{
this.description = Description_in;
}
protected string description;
public string Description
{
get
{
return this.description;
}
}
}
AttributeTargets.Class 它规定了Help特性只能被放在class的前面。这也就意味着下面的代码将会产生错误:
我们可以使用AttributeTargets.All来允许Help特性被放置在任何程序实体前。可能的值是:
Assembly,Module,Class,Struct,Enum,Constructor,Method,Property,Field,Event,Interface,Parameter,Delegate。
All = Assembly | Module | Class | Struct | Enum | Constructor | Method | Property | Field | Event | Interface | Parameter | Delegate,
ClassMembers = Class | Struct | Enum | Constructor | Method | Property | Field | Event | Delegate | Interface )
下面考虑一下AllowMultiple = false。它规定了特性不能被重复放置多次
原文链接:https://blog.csdn.net/qq_38507850/article/details/79181319
原文链接:https://blog.csdn.net/xiaouncle/article/details/70216951