Use of Attributes in .NET using C#

Use of Attributes in .NET using C#

Attributes are familiar to developers who use C++ to write COM components (through their use in Microsoft’s COM Interface Definition Language [IDL]). The initial idea of an attribute was that it provided extra information concerning some item in the program that could be used by the compiler.

Attributes are supported in .NET — and now by C++, C#, and Visual Basic 2012. What is, however, particularly innovative about attributes in .NET is that you can define your own custom attributes in your source code. These user-defined attributes will be placed with the metadata for the corresponding data types or methods. This can be useful for documentation purposes, in which they can be used with reflection technology to perform programming tasks based on attributes. In addition, in common with the .NET philosophy of language independence, attributes can be defined in source code in one language and read by code written in another language.

 

CUSTOM ATTRIBUTES

You have already seen how you can define attributes on various items within your program. These attributes have been defined by Microsoft as part of the .NET Framework class library, and many of them receive special support from the C# compiler. This means that for those particular attributes, the compiler can customize the compilation process in specific ways — for example, laying out a struct in memory according to the details in the StructLayout attributes.

The .NET Framework also enables you to define your own attributes. Obviously, these attributes won’t have any effect on the compilation process because the compiler has no intrinsic awareness of them. However, these attributes will be emitted as metadata in the compiled assembly when they are applied to program elements. By itself, this metadata might be useful for documentation purposes, but what makes attributes really powerful is that by using reflection, your code can read this metadata and use it to make decisions at runtime. This means that the custom attributes that you define can directly affect how your code runs.

For example, custom attributes can be used to enable declarative code access security checks for custom permission classes, to associate information with program elements that can then be used by testing tools, or when developing extensible frameworks that allow the loading of plug-ins or modules.

 

Writing Custom Attributes

To understand how to write your own custom attributes, it is useful to know what the compiler does when it encounters an element in your code that has a custom attribute applied to it. To take the database example, suppose that you have a C# property declaration that looks like this:

[FieldName("IdentificationNumber")]

public string IdentificationNumber

{

get {

// etc.

 

When the C# compiler recognizes that this property has an attribute applied to it (FieldName), it first appends the string Attribute to this name, forming the combined name FieldNameAttribute. The compiler then searches all the namespaces in its search path (those namespaces that have been mentioned in a using statement) for a class with the specified name. Note that if you mark an item with an attribute whose name already ends in the string Attribute, the compiler will not add the string to the name a second time; it will leave the attribute name unchanged. Therefore, the preceding code is equivalent to this:

[FieldNameAttribute("IdentificationNumber")]

public string IdentificationNumber

{

get {

// etc.

The compiler expects to find a class with this name, and it expects this class to be derived directly or indirectly from System.Attribute. The compiler also expects that this class contains information governing the use of the attribute. In particular, the attribute class needs to specify the following:

 The types of program elements to which the attribute can be applied (classes, structs, properties, methods, and so on)

 Whether it is legal for the attribute to be applied more than once to the same program element.

 Whether the attribute, when applied to a class or interface, is inherited by derived classes and interfaces.

 The mandatory and optional parameters the attribute takes If the compiler cannot find a corresponding attribute class, or if it finds one but the way that you have used that attribute does not match the information in the attribute class, the compiler will raise a compilation error. For example, if the attribute class indicates that the attribute can be applied only to classes but you have applied it to a struct definition, a compilation error will occur. Continuing with the example, assume that you have defined the FieldName attribute like this:

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

public class FieldNameAttribute: Attribute

{

private string name;

public FieldNameAttribute(string name)

{

this.name = name;

}

}

The following sections discuss each element of this definition.

 

AttributeUsage Attribute

The first thing to note is that the attribute class itself is marked with an attribute — the System.AttributeUsage attribute. This is an attribute defined by Microsoft for which the C# compiler provides special support. (You could argue that AttributeUsage isn’t an attribute at all; it is more like a meta-attribute, because it applies only to other attributes, not simply to any class.) The primary purpose of AttributeUsage is to identify the types of program elements to which your custom attribute can be applied. This information is provided by the first parameter of the AttributeUsage attribute. This parameter is mandatory, and it is of an enumerated type, AttributeTargets. In the previous example, you have indicated that the FieldName attribute can be applied only to properties, which is fi ne, because that is exactly what you have applied it to in the earlier code fragment. The members of the AttributeTargets

enumeration are as follows:

 All

 Assembly

 Class

 Constructor

 Delegate

 Enum

 Event

 Field

 GenericParameter (.NET 2.0 and higher only)

 Interface

 Method

 Module

 Parameter

 Property

 ReturnValue

 Struct

 

This list identifies all the program elements to which you can apply attributes. Note that when applying the attribute to a program element, you place the attribute in square brackets immediately before the element.

However, two values in the preceding list do not correspond to any program element: Assembly and Module. An attribute can be applied to an assembly or a module as a whole, rather than to an element in your code; in this case the attribute can be placed anywhere in your source code, but it must be prefixed with the Assembly or Module keyword:

 

[assembly:SomeAssemblyAttribute(Parameters)]

[module:SomeAssemblyAttribute(Parameters)]

When indicating the valid target elements of a custom attribute, you can combine these values using the bitwise OR operator. For example, if you want to indicate that your FieldName attribute can be applied to both properties and fields, you would use the following:

 

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,AllowMultiple=false,

Inherited=false)]

public class FieldNameAttribute: Attribute

 

You can also use AttributeTargets.All to indicate that your attribute can be applied to all types of program elements. The AttributeUsage attribute also contains two other parameters, AllowMultiple and Inherited. These are specifi ed using the syntax of <ParameterName>=<ParameterValue>, instead of simply specifying the values for these parameters. These parameters are optional — you can omit them.

The AllowMultiple parameter indicates whether an attribute can be applied more than once to the same item. The fact that it is set to false here indicates that the compiler should raise an error if it sees something

like this:

[FieldName("IdentificationNumber")]

[FieldName("InsuranceNumber")]

public string IdentificationNumber

{

// etc.

If the Inherited parameter is set to true, an attribute applied to a class or interface will also automatically be applied to all derived classes or interfaces. If the attribute is applied to a method or property, it will automatically apply to any overrides of that method or property, and so on.

Specifying Attribute Parameters 

This section demonstrates how you can specify the parameters that your custom attribute takes. When the compiler encounters a statement such as the following, it examines the parameters passed into the attribute — which is a string — and looks for a constructor for the attribute that takes exactly those parameters:

[FieldName("IdentificationNumber")]

public string IdentificationNumber

{

// etc.

If the compiler finds an appropriate constructor, it emits the specified metadata to the assembly. If the compiler does not find an appropriate constructor, a compilation error occurs. As discussed, reflection involves reading metadata (attributes) from assemblies and instantiating the attribute classes they represent. Because of this, the compiler must ensure that an appropriate constructor exists that will

allow the runtime instantiation of the specified attribute.

 

In the example, you have supplied just one constructor for FieldNameAttribute, and this constructor takes one string parameter. Therefore, when applying the FieldName attribute to a property, you must supply one string as a parameter, as shown in the preceding code.

To allow a choice of what types of parameters should be supplied with an attribute, you can provide different constructor overloads, although normal practice is to supply just one constructor and use properties to define any other optional parameters, as explained next.

Specifying Optional Attribute Parameters

As demonstrated with the AttributeUsage attribute, an alternative syntax enables optional parameters to be added to an attribute. This syntax involves specifying the names and values of the optional parameters.

It works through public properties or fields in the attribute class. For example, suppose that you modify the definition of the IdentificationNumber property as follows:

[FieldName("IdentificationNumber", Comment="This is the primary key field")]

public string IdentificationNumber

{

// etc.

In this case, the compiler recognizes the <ParameterName>=<ParameterValue> syntax of the second

parameter and does not attempt to match this parameter to a FieldNameAttribute constructor. Instead, it

looks for a public property or field (although public fields are not considered good programming practice,

so normally you will work with properties) of that name that it can use to set the value of this parameter. If

you want the previous code to work, you have to add some code to FieldNameAttribute:

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

public class FieldNameAttribute: Attribute

{

private string comment;

public string Comment

{

get

{

return comment;

}

set

{

comment = value;

}

}

// etc

}

 

Custom Attribute Example: (A very good and practical example of Attribute):

Step1 : 

This section starts with the core CustomAttribute assembly. The source code is contained in the file CustomAttribute.cs, The syntax for this is given below :

 

using System;

 

namespace CustomNewAttribute

{

    [AttributeUsage(

        AttributeTargets.Class | AttributeTargets.Method,

        AllowMultiple = true, Inherited = false)]

    public class LastChangedAttribute : Attribute

    {

        private readonly DateTime _dateChanged;

        private readonly string _changes;

 

        public LastChangedAttribute(string dateChanged, string changes)

        {

            _dateChanged = DateTime.Parse(dateChanged);

            _changes = changes;

        }

 

        public DateTime DateChanged

        {

            get { return _dateChanged; }

        }

 

        public string Changes

        {

            get { return _changes; }

        }

 

        public string Issues { get; set; }

    }

 

    [AttributeUsage(AttributeTargets.Assembly)]

    public class SupportsNewCustomAttribute : Attribute

    {

    }

}

 

Build the project.

 

Step 2 : Create a second project with  the Name VectorClass.

To use these attributes, you will be using a VectorClass. Note that you need to reference the CustomNewAttribute library that you just created. You also need to indicate the corresponding namespace with a using statement so the compiler can recognize the attributes: 

Project structures are given below:

Code for VectorClass.cs is:

using CustomNewAttribute;

using System;

using System.Collections;

using System.Text;

 

[assembly: SupportsNewCustom]

 

namespace VectorClass

{

    [LastChanged("08 Aug 2016", "IEnumerable interface implemented " +

                                  "So Vector can now be treated as a collection")]

    [LastChanged("09 Sep 2016", "IFormattable interface implemented " +

                                 "So Vector now responds to format specifiers N and VE")]

    class Vector : IFormattable, IEnumerable

    {

        public double x, y, z;

 

        public Vector(double x, double y, double z)

        {

            this.x = x;

            this.y = y;

            this.z = z;

        }

 

        [LastChanged("09 Sep 2016", "Method added in order to provide formatting support")]

        public string ToString(string format, IFormatProvider formatProvider)

        {

            if (format == null)

                return ToString();

            string formatUpper = format.ToUpper();

            switch (formatUpper)

            {

                case "N":

                    return "|| " + Norm().ToString() + " ||";

                case "VE":

                    return String.Format("( {0:E}, {1:E}, {2:E} )", x, y, z);

                case "IJK":

                    StringBuilder sb = new StringBuilder(x.ToString(), 30);

                    sb.Append(" i + ");

                    sb.Append(y.ToString());

                    sb.Append(" j + ");

                    sb.Append(z.ToString());

                    sb.Append(" k");

                    return sb.ToString();

                default:

                    return ToString();

            }

        }

 

        public Vector(Vector rhs)

        {

            x = rhs.x;

            y = rhs.y;

            z = rhs.z;

        }

 

        [LastChanged("09 Sep 2016", "Method added in order to provide collection support")]

        public IEnumerator GetEnumerator()

        {

            return new VectorEnumerator(this);

        }

 

        public override string ToString()

        {

            return "( " + x + " , " + y + " , " + z + " )";

        }

 

        public double this[uint i]

        {

            get

            {

                switch (i)

                {

                    case 0:

                        return x;

                    case 1:

                        return y;

                    case 2:

                        return z;

                    default:

                        throw new IndexOutOfRangeException(

                            "Attempt to retrieve Vector element" + i);

                }

            }

            set

            {

                switch (i)

                {

                    case 0:

                        x = value;

                        break;

                    case 1:

                        y = value;

                        break;

                    case 2:

                        z = value;

                        break;

                    default:

                        throw new IndexOutOfRangeException(

                            "Attempt to set Vector element" + i);

                }

            }

        }

 

        public static bool operator ==(Vector lhs, Vector rhs)

        {

            if (System.Math.Abs(lhs.x - rhs.x) < double.Epsilon &&

                System.Math.Abs(lhs.y - rhs.y) < double.Epsilon &&

                System.Math.Abs(lhs.z - rhs.z) < double.Epsilon)

                return true;

            else

                return false;

        }

 

        public static bool operator !=(Vector lhs, Vector rhs)

        {

            return !(lhs == rhs);

        }

 

        public static Vector operator +(Vector lhs, Vector rhs)

        {

            Vector result = new Vector(lhs);

            result.x += rhs.x;

            result.y += rhs.y;

            result.z += rhs.z;

            return result;

        }

 

        public static Vector operator *(double lhs, Vector rhs)

        {

            return new Vector(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);

        }

 

        public static Vector operator *(Vector lhs, double rhs)

        {

            return rhs * lhs;

        }

 

        public static double operator *(Vector lhs, Vector rhs)

        {

            return lhs.x * rhs.x + lhs.y + rhs.y + lhs.z * rhs.z;

        }

 

        public double Norm()

        {

            return x * x + y * y + z * z;

        }

 

        #region enumerator class

        [LastChanged("09 Sep 2016", "Class created as part of collection support for Vector")]

        private class VectorEnumerator : IEnumerator

        {

            readonly Vector _theVector;      // Vector object that this enumerato refers to 

            int _location;   // which element of _theVector the enumerator is currently referring to 

 

            public VectorEnumerator(Vector theVector)

            {

                _theVector = theVector;

                _location = -1;

            }

 

            public bool MoveNext()

            {

                ++_location;

                return (_location > 2) ? false : true;

            }

 

            public object Current

            {

                get

                {

                    if (_location < 0 || _location > 2)

                        throw new InvalidOperationException(

                            "The enumerator is either before the first element or " +

                            "after the last element of the Vector");

                    return _theVector[(uint)_location];

                }

            }

 

            public void Reset()

            {

                _location = -1;

            }

 

        }

        #endregion

    }

}

 

Step 3: USING REFLECTION

In this section, you take a closer look at the System.Type class, which enables you to access information concerning the definition of any data type. You’ll also look at the System.Reflection.Assembly class, which you can use to access information about an assembly or to load that assembly into your program. Finally, you will combine the code in this section with the code in the previous section to complete the CustomNewAttribute.

Create a new project with name : LookUpCustomAttribute and project type Windows Apllication.

Code for LookUpCustomeAttribute are : 

using CustomNewAttribute;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Reflection;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

 

namespace LookUpCustomAttribute

{

    internal class WhatsNewChecker

    {

        private static readonly StringBuilder outputText = new StringBuilder(1000);

        private static DateTime backDateTo = new DateTime(2016, 08, 08);

 

        private static void Main()

        {

            Assembly theAssembly = Assembly.Load("VectorClass");

            Attribute supportsAttribute = 

                Attribute.GetCustomAttribute(

                    theAssembly, typeof(SupportsNewCustomAttribute));

            string name = theAssembly.FullName;

 

            AddToMessage("Assembly: " + name);

            if (supportsAttribute == null)

            {

                AddToMessage(

                    "This assembly does not support New Custom attributes");

                return;

            }

            else

            {

                AddToMessage("Defined Types:");

            }

 

            Type[] types = theAssembly.GetTypes();

            foreach (Type definedType in types)

                DisplayTypeInfo(definedType);

 

            MessageBox.Show(outputText.ToString(),

                            "What\'s New since " + backDateTo.ToLongDateString());

            Console.ReadLine();

        }

 

        private static void DisplayTypeInfo(Type type)

        {

            // make sure we only pick out classes

            if (!(type.IsClass))

                return;

            AddToMessage("\nclass " + type.Name);

 

            Attribute[] attribs = Attribute.GetCustomAttributes(type);

            if (attribs.Length == 0)

                AddToMessage("No changes to this class");

            else

                foreach (Attribute attrib in attribs)

                    WriteAttributeInfo(attrib);

 

            MethodInfo[] methods = type.GetMethods();

            AddToMessage("CHANGES TO METHODS OF THIS CLASS:");

            foreach (MethodInfo nextMethod in methods)

            {

                object[] attribs2 =

                    nextMethod.GetCustomAttributes(

                        typeof(LastChangedAttribute), false);

                if (attribs2 != null)

                {

                    AddToMessage(

                        nextMethod.ReturnType + " " + nextMethod.Name + "()");

                    foreach (Attribute nextAttrib in attribs2)

                        WriteAttributeInfo(nextAttrib);

                }

            }

        }

 

        private static void WriteAttributeInfo(Attribute attrib)

        {

            LastChangedAttribute lastChangedAttrib =

                attrib as LastChangedAttribute;

            if (lastChangedAttrib == null)

                return;

 

            // check that date is in range

            DateTime ChangedDate = lastChangedAttrib.DateChanged;

            if (ChangedDate < backDateTo)

                return;

 

            AddToMessage("  Changed: " +

                         ChangedDate.ToLongDateString() + ":");

            AddToMessage("    " + lastChangedAttrib.Changes);

            if (lastChangedAttrib.Issues != null)

                AddToMessage("    Outstanding issues:" +

                             lastChangedAttrib.Issues);

        }

 

        private static void AddToMessage(string message)

        {

            outputText.Append("\n" + message);

        }

    }

}

 

Output : 

 


Recommended Book for MVC : List of Some Important books for .NET Framework, C#, MVC, WCF



(Visited 143 times, 1 visits today)

Leave a Reply

Your email address will not be published. Required fields are marked *