发布时间:2024-05-23 17:01
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。 有关详细信息,请参阅反射 (C#)。
特性具有以下属性:
特性的常见用途:
WebMethod
特性标记方法,以指明方法应可通过 SOAP 协议进行调用。 有关详细信息,请参阅 WebMethodAttribute。可以将特性附加到几乎任何声明中,尽管特定特性可能会限制可有效附加到的声明的类型。 在 C# 中,通过用方括号 ([]) 将特性名称括起来,并置于应用该特性的实体的声明上方以指定特性。
在此示例中,SerializableAttribute 特性用于将具体特征应用于类:
// 不带参数的特性
[Serializable]
// 带参数的特性
[DllImport(\"user32.dll\")]
public class SampleClass
{
// Objects of this type can be serialized.
}
按照约定,所有特性名称均以“Attribute”一词结尾,以便与 .NET 库中的其他项区分开来。 不过,在代码中使用特性时,无需指定特性后缀。 例如,[Serializable]实际是[SerializableAttribute],SerializableAttribute才是此特性在.Net类库中的实际名称。
特性目标是指应用特性的实体。 例如,特性可应用于类、特定方法或整个程序集。 默认情况下,特性应用于紧跟在它后面的元素。 不过,还可以进行显式标识。例如,可以标识为将特性应用于方法,还是应用于其参数或返回值。
若要显式标识特性目标,请使用以下语法:
[target : attribute-list]
下表列出了可能的 target
值。
目标值 | 适用对象 |
---|---|
assembly |
整个程序集 |
module |
当前程序集模块 |
field |
类或结构中的字段 |
event |
事件 |
method |
方法或 get 和 set 属性访问器 |
param |
方法参数或 set 属性访问器参数 |
property |
Property |
return |
方法、属性索引器或 get 属性访问器的返回值 |
type |
结构、类、接口、枚举或委托 |
示例:
// 将特性应用于程序集
[assembly: AssemblyTitleAttribute(\"Production assembly 4\")]
// 将特性应用于模块
[module: CLSCompliant(true)]
// 将特性应用于方法1
[ValidatedContract]
int Method1() { return 0; }
// 将特性应用于方法2
[method: ValidatedContract]
int Method2() { return 0; }
// 将特性应用于方法参数
int Method3([ValidatedContract] string contract) { return 0; }
// 将特性应用于方法返回值
[return: ValidatedContract]
int Method4() { return 0; }
可通过定义特性类创建自己的自定义特性,特性类是直接或间接派生自 Attribute 的类,可快速轻松地识别元数据中的特性定义。 假设希望使用编写类型的程序员的姓名来标记该类型。 可能需要定义一个自定义 Author
特性类:
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // multiuse attribute
]
public class AuthorAttribute : System.Attribute
{
private string name;
public double version;
public AuthorAttribute(string name)
{
this.name = name;
version = 1.0;
}
}
类名 AuthorAttribute
是该特性的名称,即 Author
加上 Attribute
后缀。 由于该类派生自 System.Attribute
,因此它是一个自定义特性类。 构造函数的参数是自定义特性的位置参数。 在此示例中,name
是位置参数。 所有公共读写字段或属性都是命名参数。 在本例中,version
是唯一的命名参数。 请注意,使用 AttributeUsage
特性可使 Author
特性仅对 class 和 struct
声明有效。AllowMultiple 参数表示
一次或多次使用此自定义特性。
可按如下方式使用这一新特性:
[Author(\"P. Ackerman\", version = 1.1)]
class SampleClass
{
// P. Ackerman\'s code goes here...
}
在下面的代码示例中,某个类应用了同一类型的多个特性。
[Author(\"P. Ackerman\", version = 1.1)]
[Author(\"R. Koch\", version = 1.2)]
class SampleClass
{
// P. Ackerman\'s code goes here...
// R. Koch\'s code goes here...
}
我们虽然可以通过自定义特性来将元数据或声明性信息与代码相关联,但是在没有检索该信息并对其进行操作的情况下将没有任何价值。 通过使用反射,可以检索通过自定义特性定义的信息。 主要方法是 GetCustomAttributes
,它返回对象数组,这些对象在运行时等效于源代码特性。 此方法有多个重载版本。 有关详细信息,请参阅 Attribute。
特性规范,例如:
[Author(\"P. Ackerman\", version = 1.1)]
class SampleClass
在概念上等效于此:
Author anonymousAuthorObject = new Author(\"P. Ackerman\");
anonymousAuthorObject.version = 1.1;
但是,在为特性查询 SampleClass
之前,代码将不会执行。 对 SampleClass
调用 GetCustomAttributes
会导致按上述方式构造并初始化一个 Author
对象。 如果该类具有其他特性,则将以类似方式构造其他特性对象。 然后 GetCustomAttributes
会以数组形式返回 Author
对象和任何其他特性对象。 之后你便可以循环访问此数组,根据每个数组元素的类型确定所应用的特性,并从特性对象中提取信息。
示例:
此处是一个完整的示例。 定义自定义特性、将其应用于多个实体,并通过反射对其进行检索。
// Multiuse attribute.
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // Multiuse attribute.
]
public class Author : System.Attribute
{
string name;
public double version;
public Author(string name)
{
this.name = name;
// Default value.
version = 1.0;
}
public string GetName()
{
return name;
}
}
// Class with the Author attribute.
[Author(\"P. Ackerman\")]
public class FirstClass
{
// ...
}
// Class without the Author attribute.
public class SecondClass
{
// ...
}
// Class with multiple Author attributes.
[Author(\"P. Ackerman\"), Author(\"R. Koch\", version = 2.0)]
public class ThirdClass
{
// ...
}
class TestAuthorAttribute
{
static void Test()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}
private static void PrintAuthorInfo(System.Type t)
{
System.Console.WriteLine(\"Author information for {0}\", t);
// Using reflection.
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t); // Reflection.
// Displaying output.
foreach (System.Attribute attr in attrs)
{
if (attr is Author)
{
Author a = (Author)attr;
System.Console.WriteLine(\" {0}, version {1:f}\", a.name, a.version);
}
}
}
}
/* Output:
Author information for FirstClass
P. Ackerman, version 1.00
Author information for SecondClass
Author information for ThirdClass
R. Koch, version 2.00
P. Ackerman, version 1.00
*/
通过使用特性,可自定义结构在内存中的布局方式。 例如,可使用 StructLayout(LayoutKind.Explicit)
和 FieldOffset
特性在 C/C++ 中创建所谓的联合。
示例:
在此代码段中,TestUnion
的所有字段均从内存中的同一位置开始。
// Add a using directive for System.Runtime.InteropServices.
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestUnion
{
[System.Runtime.InteropServices.FieldOffset(0)]
public int i;
[System.Runtime.InteropServices.FieldOffset(0)]
public double d;
[System.Runtime.InteropServices.FieldOffset(0)]
public char c;
[System.Runtime.InteropServices.FieldOffset(0)]
public byte b;
}
下面是另一个示例,其中的字段从不同的显式设置位置开始。
// Add a using directive for System.Runtime.InteropServices.
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
[System.Runtime.InteropServices.FieldOffset(0)]
public long lg;
[System.Runtime.InteropServices.FieldOffset(0)]
public int i1;
[System.Runtime.InteropServices.FieldOffset(0)]
public int i2;
[System.Runtime.InteropServices.FieldOffset(8)]
public double d;
[System.Runtime.InteropServices.FieldOffset(12)]
public char c;
[System.Runtime.InteropServices.FieldOffset(14)]
public byte b;
}
两个整数字段 i1
和 i2
共享与 lg
相同的内存位置。 使用平台调用时,这种对结构布局的控制很有用。
参考:Microsofr C# 特性
Parsing error: No Babel config file detected for xxx Either disable config file checking...报错解决方法
Java中三个修饰符?【abstract、static、final】(七)
【LVI-SAM论文全文翻译】: LVI-SAM: Tightly-coupled Lidar-Visual-Inertial Odometry via Smoothing and Mapping
计算机视觉中的 CNN&Transformer&MLP Backbone网络模型设计前沿研究总结 (持续更新)
基于Paddle的计算机视觉入门教程——第7讲 实战:手写数字识别
设置Application.targetFrameRate没有起作用的原因
【6.824分布式系统笔记】LEC 5: Go, Threads, and Raft|Go协程并发问题、Raft Debug技巧