Attribute

C# 自定义属性详解
本文详细介绍了 C# 中预定义属性的使用方法,并通过示例展示了如何开发自定义属性,包括属性的定义、使用控制及运行时查询等内容。

Using Pre-defined Attributes

There is a small set of pre-defined attributes present in C#. Before learning how to create our own custom attributes, we will first look at how to use those in our code.
using System;
public class AnyClass 
{
    [Obsolete("Don't use Old method, use New method", true)]
    static void Old( ) { }
   
    static void New( ) { }
   
    public static void Main( ) 
    {
        Old( );
    }
}
Take a look at the this example. In this example we use attribute Obsolete, which marks a program entity that should not be used. The first parameter is the string, which explain why the item is obsolete and what to use instead of this. In fact you can write any othere text here. The second parameter tells the compiler to treat the use of the item as an error. Default value is false, which means compiler generates a warning for this.

When we try to compile above program, we will get an error:

AnyClass.Old()' is obsolete: 'Don't use Old method,  use New method'

Developing Custom Attributes

Now we will see how we can develop our own attributes. Here is a small recipe to create our own attributes.

Derive our attribute class from System.Attribute class as stated in C# language specification (A class that derives from the abstract class System.Attribute, whether directly or indirectly, is an attribute class. The declaration of an attribute class defines a new kind of attribute that can be placed on a declaration) and we are done.

using System;
public class HelpAttribute : Attribute
{
}
Believe me or not we have just created a custom attribute. We can decorate our class with it as we did with obsolete attribute.
[Help()]
public class AnyClass
{
}
Note: it is a convention to use the word Attribute as a suffix in attribute class names. However, when we attach the attribute to a program entity, we are free not to include the Attribute suffix. The compiler first searches the attribute in System.Attribute derived classes. If no class is found, the compiler will add the word Attribute to the specified attribute name and search for it.

But this attribute does nothing useful so far. To make it little useful let add something more in it.

using System;
public class HelpAttribute : Attribute
{
    public HelpAttribute(String Descrition_in)
    {
        this.description = Description_in;
    }
    protected String description;
    public String Description 
    {
        get 
        {
            return this.description;
                 
        }            
    }    
}
[Help("this is a do-nothing class")]
public class AnyClass
{
}
In above example we have added a property to our attribute class which we will query at runtime in last section.

 

Defining or Controlling Usage of Our Attribute

AttributeUsage class is another pre-defined class that will help us in controlling the usage of our custom attributes. That is, we can define attributes of our own attribute class.

It describes how a custom attribute class can be used.

AttributeUsage has three properties which we can set while placing it on our custom attribute. The first property is:

ValidOn
Through this property, we can define the program entities on which our custom attribute can be placed. The set of all possible program entities on which an attribute can be placed is listed in the AttributeTargets enumerator. We can combine several AttributeTargets values using a bitwise OR operation.
AllowMultiple
This property marks whether our custom attribute can be placed more than once on the same program entity.
Inherited
We can control the inheritance rules of our attribute using this property. This property marks whether our attribute will be inherited by the class derived from the class on which we have placed it.

Let's do something practical. We will place AttributeUsage attribute on own Help attribute and control the usage of our attribute with the help of it.

using System;
[AttributeUsage(AttributeTargets.Class), 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;
        }            
    }    
}
First look at AttributeTargets.Class. It states that Help attribute can be placed on a class only. This implies that following code will result in an error:
AnyClass.cs: Attribute 'Help' is not valid on this declaration type. 
It is valid on 'class' declarations only.
Now try to put in on method
[Help("this is a do-nothing class")]
public class AnyClass
{
    [Help("this is a do-nothing method")]    //error
    public void AnyMethod()
    {
    }
} 
We can use AttributeTargets.All to allow Help attribute to be placed on any program entity. Possible values are:
  • 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 )

Now consider AllowMultiple = false. This state that attribute cannot be placed multiple time.

[Help("this is a do-nothing class")]
[Help("it contains a do-nothing method")]
public class AnyClass
{
    [Help("this is a do-nothing method")]        //error
    public void AnyMethod()
    {
    }
}
It generates a compile-time error.
AnyClass.cs: Duplicate 'Help' attribute
Ok now discuss the last property. Inherited, indicates whether the attribute, when placed on a base class, is also inherited by classes that derive from that base class. If Inherited for an attribute class is true, then that attribute is inherited. However if Inherited for an attribute class is false or it is unspecified, then that attribute is not inherited.

Let suppose we have following class relationship.

[Help("BaseClass")] 
public class Base
{
}

public class Derive :  Base
{
}
We have four possible combinations:
  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false ]
  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false ]
  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true ]
  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true ]
First Case
If we Query (we will see later how to Query attributes of a class at run-time) Derive Class for Help attribute, we will not found it as the inherited attribute is set to false.
Second Case
Second case is no different as inherited attribute is set to false in this case also.
Third Case
To explain the third and fourth cases, let's add the same attribute to the derive class also.
[Help("BaseClass")] 
public class Base
{
}
[Help("DeriveClass")] 
public class Derive :  Base
{
}
Now if we Query about Help attribute, we will get the drive class attribute only as inherited is true but multiples are not allowed so the base class Help is overridden by the Derive class Help attribute.
Fourth Case
In fourth case we will get both attributes when we query our Derive class for Help attribute as both inheritance and multiples are allowed in this case.

Note: AttributeUsage attribute is only valid on classes derived from System.Attribute and both AllowMultiple and Inherited are false for this attribute.

Positional vs. Named Parameters

Positional parameters are parameters of the constructor of the attribute. They are mandatory and a value must be passed every time the attribute is placed on any program entity. On the other hand Named parameters are actually optional and are not parameters of the attribute's constructor.

To explain it in more detail, let's add another property in our Help class.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false,
 Inherited = false)]
public class HelpAttribute : Attribute
{
    public HelpAttribute(String Description_in)
    {
        this.description = Description_in;
        this.verion = "No Version is defined for this class";
    }
    protected String description;
    public String Description
    {
        get 
        {
            return this.description;
        }
    }
    protected String version;
    public String Version
    {
        get 
        {
            return this.version;
        }
        //if we ever want our attribute user to set this property, 
        //we must specify set method for it 
        set 
        {
            this.verion = value;
        }
    }
}
[Help("This is Class1")]
public class Class1
{
}

[Help("This is Class2", Version = "1.0")]
public class Class2
{
}

[Help("This is Class3", Version = "2.0", 
 Description = "This is do-nothing class")]
public class Class3
{
}
When we Query Class1 for Help attribute and its properties, we will get:

 

Help.Description : This is Class1
Help.Version :No Version is defined for this class
As we didn't define any value for Version property, the value set in the constructor is used. If no value is defined then the default value of the type is used (for example: in case int the default value is zero).

Now, Querying Class2 will result

Help.Description : This is Class2
Help.Version :  1.0
Don't use multiple constructors for optional parameter. Instead, mark them as named parameters. We called them named because when we supply their value in the constructor, we have to name them. For example, in second class, we define Help.
[Help("This is Class2", Version = "1.0")]
In the AttributeUsage example, the ValidOn parameter is a Positional parameter and Inherited and AllowMultiple are named parameters.

Note: To set the value of named parameter in the constructor of the attribute, we must supply the set method for that property otherwise it will generate a compile time error:

'Version' : Named attribute argument can't be a read only property
Now what happen when we will query Class3 for Help attribute and its properties? The result is the same compile-time error.
'Desciption' : Named attribute argument can't be a read only property
Now modify the Help class and add the set method of Description. Now the output will be:
Help.Description : This is do-nothing class 
Help.Version : 2.0
What happen behind the scene is first the constructor is called with positional parameters, and then set method is called for each named parameter. The value set in the constructor is override by the set method.

Parameters Types

The types of parameters for an attribute class are limited to:
  • bool,
  • byte,
  • char,
  • double,
  • float,
  • int,
  • long,
  • short,
  • string
  • System.Type
  • object
  • An enum type, provided that it and any types in which it is nested are publicly accessible. A one-dimensional array involving any of the types listed above

Attributes Identifiers

Let's suppose, we want to place Help attribute on entire assembly. The First Question arises where to place that Help attribute so that compiler would determine that it is placed on an entire assembly?? Consider another situation; we want to place an attribute on the return type of a method. How compiler would determine that we are placing it on a method-return-type and not on entire method??

To resolve such ambiguities, we use attribute identifier. With the help of attribute identifiers, we can explicitly state the entity on which we are placing the attribute.

For example:

[assembly: Help("this a do-nothing assembly")]
The assembly identifier before the Help attribute explicitly tells the compiler that this attribute is attached to entire assembly. The possible identifiers are
  • assembly
  • module
  • type
  • method
  • property
  • event
  • field
  • param
  • return

Querying Attributes at Run-Time

We have seen how to create attributes and how to attach them to a program element. Now its time to learn how user of our class can query this information at run-time.

To query a program entity about its attached attributes, we must use reflection. Reflection is the ability to discover type information at run time.

We can use the .NET Framework Reflection APIs to iterate through the metadata for an entire assembly and produce a list of all classes, types, and methods that have been defined for that assembly.

Remember the old Help attribute and the AnyClass class.

using System;
using System.Reflection;
using System.Diagnostics;

//attaching Help attribute to entire assembly
[assembly : Help("This Assembly demonstrates custom attributes 
 creation and their run-time query.")]

//our custom attribute class
public class HelpAttribute : Attribute
{
    public HelpAttribute(String Description_in)
    {
        //
        // TODO: Add constructor logic here
        this.description = Description_in;
        //
    }
    protected String description;
    public String Description
    {
        get 
        {
            return this.deescription;
                 
        }            
    }    
}
//attaching Help attribute to our AnyClass
[HelpString("This is a do-nothing Class.")]
public class AnyClass
{
//attaching Help attribute to our AnyMethod
    [Help("This is a do-nothing Method.")]
    public void AnyMethod()
    {
    }
//attaching Help attribute to our AnyInt Field
    [Help("This is any Integer.")]
    public int AnyInt;
}
class QueryApp
{
    public static void Main()
    {
    }
}
We will add attribute-query code to our Main method in the next two sections.

Querying Assembly Attributes

In the following code, we get the current process name and load the assembly using LoadFrom method of Assembly class. Then we use GetCustomAttributes method to get all the custom attributes attached to the current assembly. Next foreach statement iterate through all the attributes and try to the cast the each attribute as Help attribute (casting objects using the as keyword has an advantage that if the cast is invalid, we don't have to worry about an exception being thrown. What will happen instead is that the result will be null). The next line check if cast is valid and not equal to null then it display the Help property of the attribute.
class QueryApp
{
    public static void Main()
    {
        HelpAttribute HelpAttr;

        //Querying Assembly Attributes
        String assemblyName;
        Process p = Process.GetCurrentProcess();
        assemblyName = p.ProcessName + ".exe";

        Assembly a = Assembly.LoadFrom(assemblyName);

        foreach (Attribute attr in a.GetCustomAttributes(true))
        {
            HelpAttr = attr as HelpAttribute;
            if (null != HelpAttr)
            {
                Console.WriteLine("Description of {0}:/n{1}", 
                                  assemblyName,HelpAttr.Description);
            }
        }
}
}
The output of the following program is:
Description of QueryAttribute.exe:
This Assembly demonstrates custom attributes creation and 
their run-time query.
Press any key to continue

Querying Class, Method and Field Attributes

In the code below, the only unfamiliar is the first line in Main method.
Type type = typeof(AnyClass);
It gets the Type object associated with our AnyClass class using typeof operator. The rest of the code for querying class attributes is similar to the above example and don't need any explanation (I think).

For querying method's and field's attributes, we first get all the methods and fields present in the class then we query their associated attributes in the same manner as we did class attributes

class QueryApp
{
    public static void Main()
    {

        Type type = typeof(AnyClass);
        HelpAttribute HelpAttr;


        //Querying Class Attributes
        foreach (Attribute attr in type.GetCustomAttributes(true))
        {
            HelpAttr = attr as HelpAttribute;
            if (null != HelpAttr)
            {
                Console.WriteLine("Description of AnyClass:/n{0}", 
                                  HelpAttr.Description);
            }
        }
        //Querying Class-Method Attributes  
        foreach(MethodInfo method in type.GetMethods())
        {
            foreach (Attribute attr in method.GetCustomAttributes(true))
            {
                HelpAttr = attr as HelpAttribute;
                if (null != HelpAttr)
                {
                    Console.WriteLine("Description of {0}:/n{1}", 
                                      method.Name, 
                                      HelpAttr.Description);
                }
            }
        }
        //Querying Class-Field (only public) Attributes
        foreach(FieldInfo field in type.GetFields())
        {
            foreach (Attribute attr in field.GetCustomAttributes(true))
            {
                HelpAttr= attr as HelpAttribute;
                if (null != HelpAttr)
                {
                    Console.WriteLine("Description of {0}:/n{1}",
                                      field.Name,HelpAttr.Description);
                }
            }
        }
    }
}
The output of the following program is.
Description of AnyClass:
This is a do-nothing Class.
Description of AnyMethod:
This is a do-nothing Method.
Description of AnyInt:
This is any Integer.
Press any key to continue
<think>首先,用户的问题是关于编程中的“attribute”的含义、使用及相关知识。用户提到可能是编程语言中属性的概念,特别是在.NET编程上下文中。 回顾系统指令: - 使用中文回答。 - 结构清晰,逐步解决问题。 - 回答真实可靠。 - 行内数学表达式用$...$,独立公式用$$...$$。 - 生成相关问题。 - 引用段落末尾添加引用标识。 用户提供了引用: - 引用[1]:谈论Attribute类和它们的代码格式,以及在应用程序中的使用,待续。 - 引用[2]:关于.NET中的ContextAttribute,派生自Attribute,实现了一些接口。 这些引用似乎是关于.NET框架中的Attribute概念。在.NET中,Attribute是一种元数据机制,用于添加声明性信息到代码元素上(如类、方法、属性)。 用户说:“我想了解attribute相关的信息,可能是编程语言中属性的概念、使用方法、作用等”,并指定“请问编程中attribute的含义、使用及相关知识”。所以,我需要覆盖attribute的一般概念,但在引用中提到.NET,所以我应该优先考虑.NET上下文,但也要概述一般概念以避免混淆。 在编程中,“attribute”可能有歧义: - 在C#、Java等语言中,"attribute" 或 "annotation" 指的是元数据注释。 - 在Python等语言中,"attribute" 可能指类的成员变量或属性。 - 在HTML中,"attribute" 是标签的属性。 但从引用来看,用户似乎在讨论.NET的Attribute(元数据)。用户引用的文本是关于.NET的,所以焦点应该在.NET Attribute上。 关键点覆盖: 1. **含义**:解释什么是attribute。 2. **使用**:如何使用attribute。 3. **相关知识**:重要性、常见用途等。 结构化回答: - 开头定义attribute。 - 解释在.NET中的具体实现。 - 提供示例代码或描述。 - 讨论作用。 - 最后生成相关问题。 确保语言流畅、中文。 添加引用标识:如果引用用户提供的文本,在末尾添加[^1]或[^2]。用户说“上述引用仅供参考”,但我应该只在必要时引用,以支持回答。引用是针对用户提供的文本。 用户指令:"回答中引用的段落末尾自然地添加引用标识"。所以,如果我在回答中引用用户提供的引用文本,就需要添加标识。 生成相关问题:在回答结束时,添加“§§相关问题§§”部分,列出2-3个相关问题。 数学表达式:由于话题是编程,不太可能需要数学表达式。但如果有等式,比如解释概念,使用LaTeX格式。例如,如果需要,但可能性小。 完整结构: 1. 介绍attribute的概念(一般编程)。 2. 具体到.NET中的Attribute。 3. 使用方法和示例。 4. 作用和好处。 5. 相关知识(如常见attribute)。 6. 相关问题。 基于引用: - [^1]提到Attribute类和应用。 - [^2]提到ContextAttribute。 所以,我可以整合这些。 回答大纲: - **开头**:在编程中,attribute是一种机制,用于向代码元素添加元数据(额外信息)。在.NET框架中,Attribute是核心特性。 - **含义**:Attribute允许开发者在不修改代码逻辑的情况下,添加描述性信息。 - **使用**:通过定义自定义attribute类或使用内置属性。 - **示例**:例如,[Serializable]属性表示一个类可以被序列化。 - **相关知识**:讨论从Attribute派生,ContextAttribute等。 - **结尾**:总结并有相关问题。 </think>### Attribute在编程中的含义、使用及相关知识 在编程领域中,“attribute”一词有多种含义,具体取决于上下文。最常见的理解是: - **一般编程概念**:Attribute(属性)通常指附加到代码元素(如类、方法、变量)上的元数据(metadata),用于提供额外的描述性信息,而不影响代码的核心逻辑。这类似于注释,但更结构化,且能被编译器或运行时环境读取和处理。 - **特定语言实现**:在.NET框架(如C#编程语言)中,Attribute是一个核心特性,用于实现声明式编程。例如,通过attribute可以指定序列化规则、安全权限或依赖注入行为。这与Python或Java中的“装饰器”或“注解”类似,但语法和机制略有不同。 下面我将结构化地解释attribute的含义、使用及相关知识,重点参考用户提供的.NET上下文(如引用中提到Attribute类和ContextAttribute),确保回答真实可靠。 #### 1. Attribute的含义 Attribute的本质是元数据标签,它们不直接改变代码的执行逻辑,而是提供关于代码元素的附加信息。这些信息可以被编译器、IDE或运行时框架利用来实现自动化行为。 - **核心作用**:简化代码维护,提高可读性,并支持反射(runtime introspection)能力。例如,在.NET中,Attribute派生自`System.Attribute`基类,允许开发者定义自定义属性来标记类或方法[^1]。 - **常见场景**:验证输入、控制序列化、配置依赖注入或启用AOP(面向切面编程)。 - **与其他概念的区分**: - 不要与“类属性”(class properties)混淆:后者是面向对象编程中类的成员变量(如C#的`public string Name { get; set; }`),而attribute是附加到这些元素上的元数据。 - 在HTML中,attribute指标签的属性(如`<input type="text">`中的`type`),这与编程语言中的attribute概念不同。 #### 2. Attribute的使用方法 在.NET中,使用attribute需要定义自定义attribute类或使用内置attribute。基本步骤包括: - **定义Attribute类**:创建一个继承自`System.Attribute`的类。例如,定义一个简单的属性来标记版本信息: ```csharp [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public class VersionAttribute : Attribute { public string Version { get; } public VersionAttribute(string version) => Version = version; } ``` 这里,`[AttributeUsage]`指定该attribute可应用于类或方法,且不能重复应用。 - **应用Attribute**:在代码元素(如类或方法)上添加属性标签。例如: ```csharp [Version("1.0.0")] public class MyClass { [Obsolete("This method is deprecated. Use NewMethod instead.")] public void OldMethod() { } } ``` 这个例子中: - `[Version("1.0.0")]`是自定义attribute,标记类的版本。 - `[Obsolete]`是.NET内置attribute,表示方法已废弃(编译器会发出警告)。 通过反射,运行时可以读取这些attribute信息: ```csharp var attributes = typeof(MyClass).GetCustomAttributes(typeof(VersionAttribute), false); if (attributes.Length > 0) { var versionAttr = (VersionAttribute)attributes[0]; Console.WriteLine($"Version: {versionAttr.Version}"); // 输出: Version: 1.0.0 } ``` - **高级使用(如ContextAttribute)**:在引用中提到,ContextAttribute派生自Attribute,并实现`IContextAttribute`和`IContextProperty`接口,常用于控制上下文(如事务或安全性)。例如,在分布式系统中,自定义ContextAttribute可以管理对象生命周期: ```csharp public class TransactionContextAttribute : ContextAttribute { public TransactionContextAttribute() : base("Transaction") { } // 实现接口方法以处理事务逻辑 } ``` 构造器接受一个名称参数设置属性名,这在构建自定义上下文时非常有用[^2]。 #### 3. Attribute的相关知识 - **作用与好处**: - **提高代码可维护性**:通过声明式标记替代硬编码逻辑(如用`[Serializable]`代替手动实现序列化)。 - **支持反射和动态行为**:运行时可以读取attribute来实现AOP(如日志或缓存)。 - **跨语言兼容性**:.NET的attribute机制与CLS(Common Language Specification)兼容,可在不同.NET语言中使用。 - **常见内置Attribute**: - `[Serializable]`:标记类可序列化。 - `[DllImport]`:用于调用非托管代码。 - `[WebMethod]`:在ASP.NET中标记Web服务方法。 - **局限性**: - **性能开销**:过度使用反射读取attribute可能影响性能。 - **设计原则**:Attribute应保持轻量级,避免包含业务逻辑;逻辑应留给方法本身。 - **与其他技术的对比**: - **Python装饰器**:类似attribute,但Python使用`@decorator`语法(如`@property`)。 - **Java注解**:Java的`@Annotation`机制与.NET attribute等效。 - 在C++中,attribute通过`[[attr]]`语法实现(如`[[nodiscard]]`)。 总之,attribute是增强代码表达力的强大工具,尤其在.NET中,它简化了元数据管理。如果您想深入某个具体应用(如ContextAttribute),可以进一步探讨引用中的内容[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值