Virtual Developer Workshop: Containerized Development with Docker
The common language runtime allows programmers to add descriptive declarations to programming elements called Attributes. Attributes are like annotations that can be applied to assemblies, types, methods, or properties. Attributes are emitted into the assembly metadata and they can be extracted by using Reflection services of the .NET framework.
Attributes are very convenient to the programmers because, with little effort, the runtime behavior of the program elements can be modified. For example, the attribute Serializable added to any class will indicate that the class can be serialized. As another example, consider the Transaction attribute of ServicedComponent class. Just adding this attribute to the class determines its transactional behavior.
Creating Custom Attributes
1. Applying the AttributeUsage attribute
The declaration of a custom attribute itself begins with the usage of an attribute, the AttributeUsage attribute. This has three members that qualify a custom attribute:
- AttributeTargets member
- Inherited property
The Inherited property indicates whether the custom attribute can be inherited by classes that are derived from the classes to which your attribute is applied. This property takes either a true (the default) or false flag. For example, in the following code example, MyAttribute has a default Inherited value of true.
<AttributeUsage( AttributeTargets.All, Inherited := True)> _ Public Class MyAttribute Inherits Attribute End Class
- AllowMutliple property
The AllowMultiple property indicates whether multiple instances of the custom attribute can exist on an element. The value can be either true or false.
This member specifies to what program elements the custom attribute can be applied. For example, AttibuteTargets.Class indicates that the attribute can be applied to a class, and AttributeTargets.Method indicates that the attribute can be applied to a method.
Given below is a list of possible attribute targets:
|All||Attribute can be applied to any application element.|
|Assembly||Attribute can be applied to an assembly.|
|Class||Attribute can be applied to a class.|
|Constructor||Attribute can be applied to a constructor.|
|Delegate||Attribute can be applied to a delegate.|
|Enum||Attribute can be applied to an enumeration.|
|Event||Attribute can be applied to an event.|
|Field||Attribute can be applied to a field.|
|Interface||Attribute can be applied to an interface.|
|Method||Attribute can be applied to a method.|
|Module||Attribute can be applied to a module.
Note:Module refers to a portable executable file (.dll or .exe) and not a Visual Basic standard module.
|Parameter||Attribute can be applied to a parameter.|
|Property||Attribute can be applied to a property.|
|ReturnValue||Attribute can be applied to a return value.|
|Struct||Attribute can be applied to a structure; that is, a value type.|
AttributeTargets enumeration values can also be combined with a bitwise OR operation to get the preferred combination. Given below is an example where the custom attribute can be applied either to a class or a method.
<AttributeUsage (AttributeTargets.Class Or AttributeTargets.Method)>
2. Initializing Attributes
We can use overloaded constructors to initialize attribute values. If properties are defined then, a combination of named and positional parameters can be used when initializing the attribute. Typically, all mandatory parameters are positional and all optional parameters are named. For example, consider an Attribute definition as shown below:
<AttributeUsage(AttributeTargets.Class Or _ AttributeTargets.Method)> Class DeveloperInfoAttribute Inherits Attribute Private _Description As String Private _name As String Private _lastChanged As String Public ReadOnly Property Notes() As String Get Return _Description End Get End Property Public ReadOnly Property Author() As String Get Return _name End Get End Property Public Property LastChanged() As String Get Return _lastChanged End Get Set(ByVal Value As String) _lastChanged = Value End Set End Property Sub New(ByVal author As String, ByVal notes As String) _name = author _Description = notes End Sub End Class
Here, note that Author and Notes are required parameters for the attribute whereas LastChanged is an optional parameter. So, LastChanged value can be initialized by using a named parameter as shown below:
<DeveloperInfoAttribute("Manoj", "This is a simple class to test custom attributes", LastChanged:="5-May-2003")> <DeveloperInfoAttribute("Somebody", "This is a dummy method. Not to be used")>
A couple of things to note:
- Though not mandatory, it is a good practice to add the word Attribute to the end of the attribute name.
- Attributes can be used in an abbreviated fashion. Developers can choose to skip the word attribute from the name of the attribute. For example, we can have:
<DeveloperInfo("Somebody", "This is a dummy method. Not to be used")>
Retrieving Attribute Information
As mentioned earlier, attributes are emitted into the assembly and hence we can use Reflection to obtain the attributes defined for a particular programming element. More specifically, we use the GetCustomAttribute shared method of the Attribute class to obtain an instance of the custom attribute.
The code sample shown below obtains a reference to the custom attribute DeveloperInfoAttribute defined for the class TestAttr.
Dim CustAttr As DeveloperInfoAttribute CustAttr = CType(Attribute.GetCustomAttribute(GetType(TestAttr), _ GetType(DeveloperInfoAttribute)),DeveloperInfoAttribute)
Likewise, the code sample shown below gets an instance of the custom attribute defined for the method Dummy of the TestAttr class
CustAttr = CType(Attribute.GetCustomAttribute(GetType(TestAttr). _ GetMember("Dummy")(0), _ GetType(DeveloperInfoAttribute)), _ DeveloperInfoAttribute)
Putting It All Together
Attached with this article is a simple console application that demonstrates all the above concepts. Here, DeveloperInfoAttribute is a custom attribute that has three properties: Author, Notes, and LastChanged. Author and Notes are mandatory, whereas LastChanged is optional. This attribute can be applied to both classes and methods. TestAttr is a user-defined class on which attributes are defined.