公共语言运行时添加类的关键描述性声明(称为特性),以便批注编程元素(如类型、字段、方法和属性)。编译运行时的代码时,它将被转换为中间语言(MSIL),并和编译器生成的元数据一起放置在可移植可执行文件内。特性使得将额外的描述信息放到可使用运行时反射服务提取的元数据中。当你声明派生自System.Attribute
的特殊类的实例时,编译器会创建特性。
在.NET内置了很多特性,这些特性描述数据序列化、指定用于强制安全性的特性并限制通过实时(JIT)编译器进行优化,来对代码进行调优。
自定义属性声明以System.AttributeUsageAttribute属性开头,定义特性类的一些主要特性。
AttributeUsageAttribute
包含下列三个成员,它们对创建自定义属性非常重要:AttributeTargets
、Inheited
和AllowMultiple
。
成员 | 值 | 说明 |
---|---|---|
All | 32767 | 属性可以应用于任何应用程序元素。 |
Assembly | 1 | 属性可应用于程序集。 |
Class | 4 | 属性可应用于类。 |
Constructor | 32 | 属性可应用于构造函数。 |
Delegate | 4096 | 属性可应用于委托。 |
Enum | 16 | 属性可应用于枚举。 |
Event | 512 | 属性可应用于事件。 |
Field | 256 | 属性可应用于字段。 |
GenericParameter | 16384 | 属性可应用于泛型参数。目前,这个属性只能在C#,Micrsoft中间语言(MSIL)和emitted代码中。 |
Interface | 1024 | 属性可应用于接口。 |
Method | 64 | 属性可应用于方法。 |
Module | 2 | 属性可应用于模块。模块指的是可移植的执行文件(.dll或.exe),而不是Visual Basic标准模块。 |
Parameter | 2048 | 属性可应用于参数。 |
Property | 128 | 属性可应用于属性。 |
ReturnValue | 8192 | 属性可应用于返回值。 |
Struct | 8 | 属性可应用于结构;也就是值类型。 |
AttributeUsageAttribute.Inherited
属性指明要对其应用属性的类的派生类能否继承此属性。此属性使用true
(默认值)或false
标志。
public class MyAttribute : Attribute
{
//...
}
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class YourAttribute : Attribute
{
//...
}
AttributeUsageAttribute.AllowMultiple
属性指明元素能否包含属性的多个实例。如果设置为true
,则允许多个实例。如设置为false(默认值),那么只允许一个实例。
/// <summary>
/// AllowMultiple =false 不允许多个属性
/// </summary>
[AttributeUsage(AttributeTargets.Method,AllowMultiple =false)]
public class MyAttribute:Attribute
{
///方法体
}
/// <summary>
/// AllowMultiple =false 允许多个属性
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MultipleMyAttribute : Attribute
{
///方法体
}
当应用这些特性的多个实例时,MyAttribute会生成编译器错误吗。以下代码示例显示MultipleMyAttribute的有效以及MyAttribute无效用法:
public class AttributeService
{
// This produces an error.
// Duplicates are not allowed.
[MyAttribute()]
[MyAttribute()]
public void Get() {}
// This is valid.
[MultipleMy]
[MultipleMy]
public void Update() { }
}
如果AllowMultiple属性和Inherited属性都设置为true,从另一个类继承的类可以继承一个属性,并具有在同一个子类中应用相同属性的另一个实例。如果AllowMultiple设置为false,则父类中的所有特性的值将被子类中一特性的新实例覆盖。
应用AttributeUsageAttribute以后,开始定义属性的细节。特性类的声明类似与传统类的声明。如以下代码所示:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,AllowMultiple =false)]
public class MyAttribute:Attribute
{
///
}
此特性定义说明了以下几点:
Attribute
结束。官方虽然没有要求,但是扔建议执行此约定以保证可读性。应用特性时,可以选择是否包含单词Attribute
。System.Attribute
类继承。和传统类类似,特性是通过构造函数初始化的。下面的代码段阐明了典型的特性构造函数。此公共构造函数采用一个参数,并设置一个等于其值的成员变量。
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,AllowMultiple =false)]
public class MyAttribute:Attribute
{
private string myValue;
public MyAttribute(string myValue)
{
this.myValue = myValue;
}
}
可以重载此构造函数以适应值的各种组合。如果你还未自定义特性类定义了属性,则在初始化该特性时可以使用命名参数和定位参数的组合。通常情况下,将所有必须的参数定义为定位参数,将所有可选的参数定义为命名参数。在这种情况下,没有必需的参数就无法初始化属性。其他所有参数都是可选参数。
如果你想要定义一个命名参数,或者提供一种简单的方法来返回由特性存储的值,请声明属性。应将特性的属性声明为公共实例,此公告实体包含将返回的数据类型的描述。定义将保存属性值的变量,并将此变量与get和set方法相关联。
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,AllowMultiple =false)]
public class MyAttribute:Attribute
{
public string AttributeName
{
get;
set;
}
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,AllowMultiple =false)]
public class MyAttribute:Attribute
{
private string author;
private DateTime data;
private string version;
public MyAttribute(string author, DateTime data)
{
this.author = author;
this.data = data;
}
public virtual string Author
{
get { return author; }
}
public virtual DateTime Data
{
get { return data; }
}
public string Version
{
get { return version; }
set { version = value; }
}
}
可以采用以下任意一种方法,调用自定义特性:
public class AttributeService
{
[MyAttribute("AuthorName","2023-12-18 15:14:01")]
public void Get(){}
[MyAttribute("Author2","2023-12-18 16:14:49",Version="v1.0")]
public void Update() { }
}
特性提供声明式编程能力,但是它们是一种元数据形式的代码,本身并不执行任何操作。可以通过反射来调用特性完成操作。
下一章将详解如何使用反射调用特性。