C#:特性

发布时间:2024-05-23 17:01

特性介绍


使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。 有关详细信息,请参阅反射 (C#)。

特性具有以下属性:

  • 特性向程序添加元数据。 元数据是程序中定义的类型的相关信息。 所有 .NET 程序集都包含一组指定的元数据,用于描述程序集中定义的类型和类型成员。 可以添加自定义特性来指定所需的其他任何信息。
  • 可以将一个或多个特性应用于整个程序集、模块或较小的程序元素(如类和属性)。
  • 特性可以像方法和属性一样接受自变量。
  • 程序可使用反射来检查自己的元数据或其他程序中的元数据。

特性的常见用途:

  • 在 Web 服务中使用 WebMethod 特性标记方法,以指明方法应可通过 SOAP 协议进行调用。 有关详细信息,请参阅 WebMethodAttribute。
  • 描述在与本机代码互操作时如何封送方法参数。 有关详细信息,请参阅 MarshalAsAttribute。
  • 描述类、方法和接口的 COM 属性。
  • 使用 DllImportAttribute 类调用非托管代码。
  • 从标题、版本、说明或商标方面描述程序集。
  • 描述要序列化并暂留类的哪些成员。
  • 描述如何为了执行 XML 序列化在类成员和 XML 节点之间进行映射。
  • 描述的方法的安全要求。
  • 指定用于强制实施安全规范的特征。
  • 通过实时 (JIT) 编译器控制优化,这样代码就一直都易于调试。
  • 获取方法调用方的相关信息。

使用示例:


可以将特性附加到几乎任何声明中,尽管特定特性可能会限制可有效附加到的声明的类型。 在 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  
*/

使用特性创建 C/C++ 联合


通过使用特性,可自定义结构在内存中的布局方式。 例如,可使用 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# 特性

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号