In .NET code is compiled to an assembly. The assembly contains IL code and metadata. IL is a state between source code and machine language. By emitting IL the CLR has one more opportunity to examine code before it is just-in-time compiled (JITted) and to compare the security permissions requested against those granted. Also, the total number of compilers and JITters is dramatically reduced (see the note). In addition to IL, .NET assemblies contain metadata. Metadata allows assemblies to carry additional information around that uniquely identifies the assembly. Metadata is added to assemblies via attributes.
The concept of metadata was implemented for .NET to help relieve "DLL hell." The role of attributes is to associate metadata, or extra information, with an assembly. Metadata mitigates the need to store information that uniquely identifies an application in a separate location, the registry. Attributes play a central role here.
Reviewing Attribute Conventions
Like everything else in .NET, attributes are just classes. The good news is that if you know how to use classes, you have won half the battle. (We'll cover conventions for custom attributes in the Creating Custom Attributes section later in the chapter.)
By convention attributes are classes with an Attribute suffix. When you apply an attribute, by convention you drop the Attribute suffix. For example, if you apply the DefaultMemberAttribute to a class, you drop the Attribute suffix and apply the attribute using attribute notation, as demonstrated below.
<DefaultMember("Item")> Public Class HasDefaultMember ...
The attribute is applied in the same line as the entity it is applied to in Visual Basic .NET. If you want to apply the attribute in the preceding line, you must use the line continuation character ( _ ). Here is the DefaultMemberAttribute applied to HasDefaultMember using the line continuation character.
<DefaultMember("Item")> _ Public Class HasDefaultMember ...
It is important to recognize that you are invoking a constructor call with the attribute syntax. The parameters you are passing, called positional arguments or named arguments (not shown in the code fragment above), are parameters to the Attribute class constructor. Refer to the Creating Custom Attributes section for more information on positional and named arguments.
Applying Attributes to Entities
The most common use of attributes is to apply them to assemblies, classes, other types, or members . Since there are a lot of attributes (and we have the ability to create additional custom attributes), I won't make any effort to exhaustively list available attributes. Instead I will demonstrate several examples that show how to apply attributes and introduce a few interesting attributes, so you will know how to apply any attribute you encounter.
Using the DefaultMemberAttribute , DefaultPropertyAttribute , and Default Modifier
The DefaultMemberAttribute is applied to indicate that a member of a class is the default member that will be called when the InvokeMember method is invoked. For example, if you obtain a Type object for a specific type and use that object to call InvokeMember , you can leave blank the member name argument for InvokeMember . If a default member exists, it will be called by InvokeMember . Listing 5.1 offers an example.
Listing 5.1 Implementing and Invoking Default Members Using Reflection
<DefaultMember("IsDefault")> _ Public Class HasDefaultMember Public Shared Sub IsDefault() Console.WriteLine("IsDefault DefaultMember invoked") End Sub End Class Public Sub TestDefaultMember() Dim t As Type = _ Type.GetType("DefaultMemberDemo.HasDefaultMember") ' We don't need the default member's name t.InvokeMember("", BindingFlags.Static Or _ BindingFlags.Public Or BindingFlags.InvokeMethod, _ Nothing, Nothing, Nothing) End Sub
HasDefaultMember in Listing 5.1 demonstrates how to apply the DefaultMemberAttribute to the class, supplying the name of the member that is the default member. The second method ” TestDefaultMember ”demonstrates how to invoke the default member using Reflection. (Chapter 4 covered Reflection in depth.) Notice that we did not need to express the member name ”the first argument to InvokeMember ”to invoke the default member. The DefaultPropertyAttribute is applied at the class level too. (Technically it is limited to the class level because of the AttributeUsageAttribute used when the DefaultPropertyAttribute was defined. Refer to the Creating Custom Attributes section later in this chapter for more information.) The DefaultPropertyAttribute is defined in the System.ComponentModel namespace and is initialized with the name of the component property that will be the default selected property in the Properties window when the control is selected. The DefaultPropertyAttribute is applied identically to the DefaultMemberAttribute . (Refer to Chapter 9 for more information.)
If you are trying to make a property behave as the default property, for example, when you use an object in a context where a property is expected, you want to use the Default modifier. The Default modifier indicates that a property is the default property; however, this modifier can be applied only to indexed properties in .NET. The reason for this is that VB6 used Set , Get , and Let , which acted as cues to the compiler that you were referring to a property when no property was typed in the code. VB .NET does not support Get , Set , and Let for properties, but a cue is still needed. Thus, the array operators play the role of cues to the compiler. Listing 5.2 demonstrates how to define a class that uses the Default modifier and a client method that interacts with the default property.
Listing 5.2 Using the Default Property Modifier on Indexed Properties
Public Class HasDefault Inherits ReadOnlyCollectionBase Public Shared Function CreateNew() As HasDefault Dim Instance As HasDefault = New HasDefault() Instance.InnerList.Add("Never ") Instance.InnerList.Add("mistake ") Instance.InnerList.Add("a ") Instance.InnerList.Add("clear ") Instance.InnerList.Add("view ") Instance.InnerList.Add("for ") Instance.InnerList.Add("a ") Instance.InnerList.Add("short ") Instance.InnerList.Add("distance") Instance.InnerList.Add("!") Return Instance End Function Default Public ReadOnly Property Item(ByVal Index As Integer) As String Get Return CType(InnerList(Index), String) End Get End Property End Class Public Sub TestDefault() Dim HasDefault As HasDefault = HasDefault.CreateNew Dim I As Integer For I = 0 To HasDefault.Count - 1 Console.Write(HasDefault(I)) Next Console.WriteLine() End Sub
The Default modifier is employed in the ReadOnly property Item defined in the HasDefault class that inherits from ReadOnlyCollectionBase . ( ReadOnlyCollectionBase is defined in System.Collections ; hence, you will have to use an Imports statement with the code as it is.) The TestDefault subroutine creates an instance of the HasDefault class (by using the factory method CreateNew ) and uses a For loop to iterate over all the elements in the HasDefault object. Notice that I am not explicitly referring to the Item property in the line of code Console.Write(HasDefault(I)) ; however, if you place a breakpoint in the Item getter method, you will see that the Default property is being invoked. This is true even though the code looks as if we are referring to an array of HasDefault objects.
Using the Obsolete Member Attribute
The ObsoleteAttribute is used to mark elements of code that are being deprecated in future versions and will no longer be supported. The basic version of the ObsoleteAttribute requires no parameters and will display a message in the Task List indicating that the element is obsolete (see Figure 5.1). Listing 5.3 demonstrates an example of the ObsoleteAttribute .
Listing 5.3 Using the ObsoleteAttribute
<Obsolete()> _ Public Sub TestDefault() Dim HasDefault As HasDefault = HasDefault.CreateNew Dim I As Integer For I = 0 To HasDefault.Count - 1 Console.Write(HasDefault(I)) Next Console.WriteLine() End Sub
Figure 5.1. A message in the Task List indicating elements marked as obsolete.
You can pass a custom string message and a Boolean indicating whether the obsolete element is treated as an error by the compiler. The ObsoleteAttribute can be applied to all elements of code except parameters, return values, modules, and assemblies.
There are a large number of existing attributes, and because attributes are just classes you can expect that number to grow. The most important thing is to realize how an attribute is applied and then to look for attributes just as you would look for preexisting code when you have a particular problem to solve. To recap, when you apply an attribute, you are essentially constructing an instance of that attribute's class, passing the positional and named arguments to initialize the class.
How attributes are used is dictated by the AttributeUsageAttribute and the AttributeTargets enumeration. Refer to the Specifying Attribute Usage section later in this chapter for more information.