Attributes in VB.NET - Part One

Store and use information in the assembly metadata.

Typing on Keyboard
blackred/E+/Getty Images

Attribute is a word that has been overloaded. Overloading is not just an object oriented programming technique. It's also something that happens to ordinary words when you're trying to figure out how to use a sophisticated development environment like VB.NET. Depending on your previous experience, you might have run into attributes before. In VB.NET, it's a feature that you'll see more frequently as you learn advanced programming techniques.

Just to make sure that you understand what this article is about, let's look what the article is not about first to get it out of the way. You might have a background in XML or HTML. In those languages, an attribute is a "modifier of an element". For example, consider this HTML anchor tag:


<a href="http://visualbasic.about.com/index.htm">About Visual Basic</a>

href is an HTML attribute of the anchor tag element named a.

Although the concept is similar, this article is not about XML or HTML attributes. Instead, this article explains how to use attributes in VB.NET code to "declare" information or processing that is saved with the code definition. You might see other articles that say attributes add "a declarative dimension to designing and writing software". I'll explain just what that means.

Here's the book description of VB.NET attributes straight from Microsoft:

Attributes are keyword-like tags in which you can specify additional information about entities defined in Visual Basic .NET applications. Attributes, which are saved with an assembly's metadata, annotate programming elements such as types, fields, methods, and properties.

Another definition ... maybe a little easier to understand ... is that an attribute is simply a class that inherits from System.Attribute. This one is short on detail but I'll start supplying that detail on the next page where we cover the two types of attributes in VB.NET: Predefined Attributes and Custom Attributes.

In general, there are two types of attributes in VB.NET: predefined and custom. A predefined attribute is defined and used by .NET itself. For example, the Common Language Runtime (CLR) uses an attribute - AssemblyVersionAttribute - to use the right features supported by an assembly at runtime. VB.NET uses the DefaultValueAttibute to set the value of a type - for example, an Integer variable will default to 0 - at design time.

One of the most commonly used attributes is the SerializableAttribute. This lets programmers tell the CLR that instances of a class can be serialized, that is, converted into a bit stream or XML characters so it can be saved in a file or transmitted in a network. As a starting example, this is what SerializableAttribute looks like in a code example:


' Instances of this class need to be serialized
<Serializable()> Public Class theClass
' Code in the class
End Class

For example, suppose this class is a "CustomerAccounts" class. Instances of this class will probably contain information (properties) like the customer's name and account number. To send the whole instance of a CustomerAccounts class over a network, it has to be declared as <Serializable()>.


' Instances CustomerAccounts
' must be sent over a network
<Serializable()> Public Class CustomerAccount
    Public CustomerName As String
    Public CustomerAccountNumber As Integer
    Sub New(ByVal CustName, ByVal CustAcct)
        ' Code to create a new instance
    End Sub
End Class

' Example code using this class
Dim theCustName As String
Dim theCustAcct As Integer
theCustName = "George"
theCustAcct = 12345
Dim theCustomer
    As New CustomerAccount(
        theCustName, theCustAcct)
' Code to send theCustomer over a network,
' write it to a file, or some other action
' that requires a bit stream rather than an
' object would be placed here.

There are predefined attributes for different entities from the top to the bottom in .NET. You can use an attribute to modify assemblies, types, methods, properties, classes, and structures. The syntax is the same (shown below) except for assemblies and assembly modules (an assembly module is not a VB.NET module).

In those cases, you have to specify a target for the attribute as shown below (extracted from Microsoft's documentation):


Imports System.Reflection
<Assembly: AssemblyTitleAttribute(
    "Production assembly 4"),
Module: CLSCompliant(True)>
Public Class Whatever
' The class code continues ....

Again, Microsoft's documentation can be confusing. A chart on the MSDN page shows "Not supported" for VB for everything except assemblies and assembly modules. That only means that the target parameter syntax shown above isn't used in VB, not that you can't use an attribute for that target.

In addition to the predefined attributes in .NET, you can take advantage of attributes in the code you write using custom attributes.

Before you code your own custom attribute, think about how the predefined attributes you have seen so far are used to get the right idea in mind. Since an attribute is hard coded in the attribute definition, the information in the attribute must make sense for different instances of the target.

For example, all of the instances of an assembly will have a version number. AssemblyVersionAttribute just lets you insert this number into the assembly metadata. An example at Microsoft's site shows how to code an "Author" attribute for a class. Every class will have at least one author. All instances of that class will have the same author. This isn't a property where the information is supposed to be dynamic.

If you check out the hidden files in your project, you'll see that Microsoft uses other attributes in the generated code. The meta-information about a project - the information in the project properties pages - is kept in a file named AssemblyInfo.vb that is nothing except attributes for that project.

To nail down the concept of attributes in VB.NET, let's look at how the predefined VBFixedString attribute is used in a real program. The problem that this attribute solves is created by the fact that in VB.NET, a string is really an array of Char types.

To show the effect of this, consider a file containing a structure composed of strings. If you create this file using FilePut, you don't get what you might expect. VB.NET adds extra information because it's an array of Chars. Consider this example program. (The PrefixString and PostfixString variables are there just to provide a visual marker in the created file.)


Structure VariableType
    Public PrefixString As String
    Public myString As String
    Public PostfixString As String
End Structure

Private Sub Button1_Click(
        ByVal sender As System.Object,
        ByVal e As System.EventArgs
        ) Handles Button1.Click
    Dim myRecord As VariableType
    FileOpen(1, "F:\TESTFILE.TXT",
        OpenMode.Binary)
    myRecord.PrefixString = "X"
    myRecord.myString = "AAAAA"
    myRecord.PostfixString = "X"
    FilePut(1, myRecord)
    FileClose(1)
End Sub

--------
Click Here to display the illustration
--------

When the resulting program is executed, here's what the file looks like in Notepad. Notice the unprintable extra characters. To solve this problem and get rid of the extra characters, you can add the VBFixedString attribute. (Dim myRecord As FixedType must also be changed.)


Structure FixedType
    <VBFixedString(1)> Public PrefixString As String
    <VBFixedString(5)> Public myString As String
    <VBFixedString(1)> Public PostfixString As String
End Structure

--------
Click Here to display the illustration
--------

This results in what you need - a file with just the characters in the string that you can use in other systems.

Using the .NET predefined attributes is relatively simple.

You just include them in front of the target. Creating your own custom attributes is another thing entirely. The main problem is that a really good reason to use one just doesn't pop up all the time. I'll return to this theme several times in the discussion below because it's a concept that you might not fully understand immediately.

Getting started with a custom attribute is easy. On the first page, we defined an attribute as "a class that inherits from System.Attribute." That makes the simplest attribute:


Public Class SimpleAttribute
    Inherits System.Attribute
    '  There's no code!
End Class

And, in fact, if you code and build this as a class library (which I named SimpleAttribute, the same as the class name), you can add a reference to the DLL (in the \bin\Release folder) and apply it to another project like this:


<Assembly: SimpleAttribute.Simple()>
Public Class Form1
    ' There's no code here either!
End Class

None of this code does anything, of course, but it compiles and runs. One thing that might be confusing, however, is that although the attribute class is named SimpleAttribute, it's just called Simple in the code where it's applied. Microsoft's convention - implemented by VB.NET automatically - is to use the suffix "Attribute" as part of the name when you define them but not to require that suffix when they're applied. In other words, the code below is completely equivalent to the code above.


<Assembly: SimpleAttribute.SimpleAttribute()>
Public Class Form1
    ' There's no code here either!
End Class

Visual Studio Intellisense drops the "Attribute" suffix by default. The convention of dropping part of the name in the syntax like this is another thing that can be confusing about attributes.

Another thing to keep in mind is that it usually makes more sense to define the attribute in a class library file that is separate from the the applications that use it. You could define and use the attribute in the same file. The examples at MSDN are done that way for simplicity. But because you will probably want to use the attributes in more than just one project, it's a good idea to keep them in a separate class library assembly.

The main problem with custom attributes is finding a really good application. Remember, an attribute isn't a property. It's information that can be used with every instance of a particular class that you define. Nearly all of the really useful attributes are predefined for you in .NET, like the assembly version or the Copyright string. You don't have to use the reflection code to retrieve them, either.

You can use the simple "My" object and your code is much simpler.

I decided that this was a problem when I searched the literature to see what other authors have used as examples of custom attributes. MSDN uses an Author attribute in their example. Paul Kimmel uses a definition of a control (height, width, control type and so forth) in several books and creates code that will generate standardized controls. (I found it to be fairly complex and probably of limited value.) J. P. Hamilton has a better example in his book, Object Oriented Programming with Visual Basic .NET. He duplicates the C# Summary tag with an attribute that allows you to embed a special comment in the class code that can be extracted and printed as a form of documentation.

It appears that MSDN's example adding an Author to the metadata stored for a class is the simplest example. (But stay tuned. I think I found a use for attributes that you might find actually valuable in your projects.

I'll detail it in the next article in this two part series.) So, even though I'm duplicating what you can read at their site, I'll explain the rest of the attribute syntax using their example.

MSDN provides this example attribute definition.


<System.AttributeUsage(System.AttributeTargets.Class Or 
                       System.AttributeTargets.Struct)>
Public Class Author
    Inherits System.Attribute
    Private name As String
    Public version As Double
    Sub New(ByVal authorName As String)
        name = authorName
        version = 1.0
    End Sub
End Class

System.AttributeUsage is itself an attribute that can be applied to classes that define custom attributes. AttributeTargets is the only positional parameter and was introduced earlier in the discussion of predefined attributes. Note that you can include several target types connected by an Or. (There are two in the example code.)

There are also two named parameters, AllowMultiple and Inherited. Both take a True or False value. AllowMultiple lets you specify the same attribute more than once. In this case, if you have several authors for the same class. Inherited controls whether subclasses of this class will inherit the attribute. (If someone inherits this class, it shows whether the class should also have this author.)

The named parameter syntax, in this case for AllowMultiple, is coded this way:

<System.AttributeUsage(System.AttributeTargets.Class Or 
                       System.AttributeTargets.Struct, 
                       AllowMultiple:=True)>

In this example, Microsoft violates their own convention by not including the "Attribute" suffix as part of the class name. But that's OK. It works anyway.

The difference between the name and version attribute properties is another key part of their example:


Private name As String
Public version As Double

The name property is a required part of the New subroutine and so it's set just once when the class is compiled. The version property can be set using the named parameter syntax shown earlier:


<Author("R. Koch", Version:=1.2)> 

The other part of using custom attributes is using reflection to access the information again. With predefined attributes, parts of .NET like the CLR (Common Language Runtime) and the VB compiler read the attributes and use them. When you define your own, you have to write the code to use them too.

In the part two of this series will define a new attribute that you might be able to use in your own systems, and will show you how to use that attribute again.

Here's the setup.

Suppose you have a requirement for users of your system to return information securely and secretly.

One of the best ways to do this is with asymetric encryption, also called public-private key encryption. You send a public key to a user and the user encrypts the message with that key and returns it. But it can be made much easier to use if the public key is simply embedded into the metadata of a class as an attribute. Part two shows how it's done.