In addition to the custom attributes that you use to annotate regular C# types, you can use the AttributeUsage attribute to define how you want these attributes to be used. The AttributeUsage attribute has the following documented calling conventions:
[AttributeUsage( validon, AllowMultiple = allowmultiple, Inherited = inherited )]
As you can see, it�s easy to discern positional parameters from named parameters. We strongly recommend that you document your attributes in this fashion so that users of your attributes don�t need to look through your attribute classes� source code to find the public read and write fields and properties that can be used as named attributes.
Looking again at the AttributeUsage attribute, notice that the validon parameter is a positional parameter�and therefore, mandatory. This parameter enables you to specify the types on which your attribute can be attached. Actually, the validon parameter in the AttributeUsage attribute is of the type AttributeTargets, which is an enumeration defined as follows:
public enum AttributeTargets
{
Assembly = 0x0001,
Module = 0x0002,
Class = 0x0004,
Struct = 0x0008,
Enum = 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x0200,
Interface = 0x0400,
Parameter = 0x0800,
Delegate = 0x1000,
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,
}Notice when using the AttributeUsage attribute that you can specify AttributeTargets.All so that the attribute can be attached to any of the types listed in the AttributeTargets enumeration. If you don�t specify the AttributeUsage attribute, AttributeTargets.All is the default. Given this default, you might be wondering why you�d ever use the validon value. The answer is that you can use named parameters on this attribute and you might want to change one of them. Remember that if you use a named parameter, you must precede it with all the positional parameters. This gives you an easy way to specify that you want the default attribute usage of AttributeTargets.All, and it still lets you set the named parameters.
When would you specify the validon (AttributeTargets) parameter and why? You use it when you want to control exactly how an attribute is used. In the earlier examples, we created a RemoteObjectAttribute attribute that�s applicable to classes only, a TransactionableAttribute attribute that applies only to methods, and a RegKeyAttribute attribute that makes sense only with fields. If we want to make sure that these attributes are used to annotate only the types for which they were designed, we can define them as follows (attribute bodies left out for brevity):
[AttributeUsage(AttributeTargets.Class)]
public class RemoteObjectAttribute : Attribute
{
�
}
[AttributeUsage(AttributeTargets.Method)]
public class TransactionableAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field)]
public class RegKeyAttribute : Attribute
{
}
[RemoteObject(RemoteServers.COSETTE)]
class SomeClass
{
[Transactionable]
public void Foo() {}
[RegKey(RegHives.HKEY_CURRENT_USER, "Bar")]
public int Bar;
}We can then use reflection to report on these various custom attributes:
Create this object on COSETTE. Foo is transactionable. Bar will be saved in HKEY_CURRENT_USER\\Bar
One last point regarding the AttributeTargets enumeration: you can combine members by using the OR (?) operator. If you have an attribute that applies to both fields and properties, you can attach the AttributeUsage attribute to it as follows:
[AttributeUsage(AttributeTargets.Field ? AttributeTargets.Property)]
You can use AttributeUsage to define attributes as either single-use or multiuse. This decision pertains to how many times you can use a single attribute on a single field. By default, all attributes are single-use, meaning that compiling the following will result in a compiler error:
public class SomethingAttribute : Attribute
{
public SomethingAttribute(String str)
{
}
}
// Error: "Duplicate single-use Something attribute"
[Something("abc")]
[Something("def")]
class MyClass
{
}To fix this problem, specify on the AttributeUsage line that you want to allow the attribute to be attached to a given type multiple times. This code would work:
[AttributeUsage(AttributeTargets.All, AllowMultiple=true)]
public class SomethingAttribute : Attribute
{
public SomethingAttribute(String str)
{
}
}
[Something("abc")]
[Something("def")]
class MyClass
{
}For example, you might want to use this approach with the RegKeyAttribute attribute presented in the �Defining Attributes� section earlier in this part. Because it�s conceivable that a field might be saved in multiple places in the Registry, you could attach the AttributeUsage attribute with the AllowMultiple named parameter, as in the preceding code.
The last parameter for the AttributeUsageAttribute attribute is the inherited flag, which dictates whether the attribute can be inherited. The default for this value is false. However, if the inherited flag is set to true, its meaning depends on the value of the AllowMultiple flag. If the inherited flag is set to true and the AllowMultiple flag is false, the attribute will override the inherited attribute. However, if the inherited flag is set to true and the AllowMultiple flag is also set to true, the attribute accumulates on the member. Table 6-1 summarizes these permutations.
|
Inherited |
AllowMultiple |
Result |
|
true |
false |
Derived attribute overrides base. |
|
true |
true |
Derived and base attributes are combined. |
For example:
using System;
using System.Reflection;
namespace AttribInheritance
{
[AttributeUsage(
AttributeTargets.All,
// AllowMultiple=true,
AllowMultiple=false,
Inherited=true
)]
public class SomethingAttribute : Attribute
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public SomethingAttribute(string str)
{
this.name = str;
}
}
[Something("abc")]
class MyClass
{
}
[Something("def")]
class Another : MyClass
{
}
class Test
{
[STAThread]
static void Main(string[] args)
{
Type type =
Type.GetType("AttribInheritance.Another");
foreach (Attribute attr in
type.GetCustomAttributes(true))
// type.GetCustomAttributes(false))
{
SomethingAttribute sa =
attr as SomethingAttribute;
if (null != sa)
{
Console.WriteLine(
"Custom Attribute: {0}",
sa.Name);
}
}
}
}
}With AllowMultiple set to false, the result is:
Custom Attribute: def
whereas with AllowMultiple set to true, the result is:
Custom Attribute: def Custom Attribute: abc
Note that if you pass false to GetCustomAttributes, it won�t walk the inheritance tree, so you�ll still get only the derived class attributes.