分享
 
 
 

Using Attributes in C#

王朝c#·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

原文出处:http://www.codeguru.com/Csharp/Csharp/cs_syntax/attributes/article.php/c5831/

Using Attributes in C#

Rating: none

Sadaf Alvi (view profile)

September 24, 2002

Environment: C#

Introduction Attributes are a new kind of declarative information. We can use attributes to define both design-level information (such as a help file or a URL for documentation) and run-time information (such as associating an xml field with a class field). We also can create "self-describing" components using attributes. In this tutorial, we will see how we can create and attach attributes to various program entities, and how we can retrieve attribute information in a run-time environment.

Definition

(continued)

As stated in MSDN (ms-help://MS.MSDNQTR.2002APR.1033/csspec/html/vclrfcsharpspec_17_2.htm):

"An attribute is a piece of additional declarative information that is specified for a declaration." 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. We use the attribute Obsolete, which marks a program entity that should not be used. The first parameter is the string, which explains why the item is obsolete and what to use instead. In fact, you can write any other text here. The second parameter tells the compiler to treat the use of the item as an error. The default value is false, which means the compiler generates a warning for this.

When we try to compile the preceding 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 the System.Attribute class as stated in the 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 an 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 a little more useful, let's add something more in it.

using System;

public class HelpAttribute : Attribute

{

public HelpAttribute(String Description_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. We will query it at runtime in the last section.

Defining or Controlling the Usage of Our Attribute AttributeUsage class is another pre-defined class that will help us control 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 that we can set when placing it on our custom attribute. These three properties are discussed in the following sections.

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 by 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 by 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 the AttributeUsage attribute on our 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 the AttributeTargets.Class. It states that the Help attribute can be placed on a class only. This implies that the 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 it in a 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 the 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 states that an attribute cannot be placed multiple times.

[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

Okay, now let's 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, that attribute is inherited. However, if Inherited for an attribute class is false or it is unspecified, that attribute is not inherited.

Let's 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 ] These combinations are discussed in the following sections.

First Case If we query (we will see later how to query the attributes of a class at run-time) the Derive Class for its Help attribute, we will not find it because the inherited attribute is set to false.

Second Case The second case is not different; an 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 the Help attribute, we will get the drive class attribute only because inherited is true, but multiples are not allowed so the base class Help is overridden by the Derive class Help attribute.

Fourth Case In the fourth case, we will get both attributes when we query our Derive class for the Help attribute because both inheritance and multiples are allowed in this case.

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

Positional versus Named Parameters Positional parameters are parameters of the attribute's constructor. 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 to our Help class.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false,Inherited = false)]

public class HelpAttribute : Attribute

{

public HelpAttribute(String Description_in)

{

this.description = Description_in;

this.version = "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.version = 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 the Help attribute and its properties, we will get:

Help.Description : This is Class1

Help.Version :No Version is defined for this class

Because we didn't define any value for the Version property, the value set in the constructor is used. If no value is defined, the default value of the type is used (for example, in case int, the default value is zero).

Now, querying Class2 will result in the following:

Help.Description : This is Class2

Help.Version : 1.0

Don't use multiple constructors for optional parameters. 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, such as the following message:

'Version' : Named attribute argument can't be a read-only property

Now what happens when we query Class3 for the Help attribute and its properties? The result is the same compile-time error.

'Description' : 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 happens behind the scenes is first the constructor is called with positional parameters, and then the set method is called for each named parameter. The value set in the constructor is overriden by the set method.

Parameter Types The types of parameters for an attribute class are limited to the following:

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 Attribute Identifiers Let's suppose that we want to place the Help attribute on the entire assembly. The first question arises: Where should we place that Help attribute so that the compiler can 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 would the compiler determine that we are placing it on a method-return-type and not on an entire method??

To resolve such ambiguities, we use an 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 the 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 it's time to learn how the 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.description;

}

}

}

//attaching the Help attribute to our AnyClass

[HelpString("This is a do-nothing Class.")]

public class AnyClass

{

//attaching the 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 the 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 the LoadFrom method of the Assembly class. Then, we use the GetCustomAttributes method to get all the custom attributes attached to the current assembly. Next, for each statement iterate through all the attributes and try to cast each attribute as a 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 checks whether the cast is valid and not equal to null; then it displays 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 following code, the only unfamiliar item is the first line in the Main method.

Type type = typeof(AnyClass);

It gets the Type object associated with our AnyClass class using the typeof operator. The rest of the code for querying class attributes is similar to the preceding example and doesn't need any explanation (I think).

For querying a 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

[url=http://www.codeguru.com/Csharp/Csharp/cs_syntax/attributes/article.php/c5831/][/url]

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有