Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# Lectures - Lecture 6: Attributes, Custom Attributes in C#

0.00/5 (No votes)
15 Aug 2016 29  
6th lecture from my series. This lecture is about attributes.

Full Lectures Set


Introduction

6th article from my series is about nice .NET technology that is called custom attributes. Custom attributes gives you the ability to add more content to source code and add more information almost to every record in metadata table for assembly. We can work with this metadata during runtime and it may impact the flow of application. Most famous technologies of .NET as WPF, WCF, etc. use attributes a lot. Every .NET engineer should be familiar with this technology to be efficient in his work with .NET and C#.

Attributes Areas

Attribute specifies declarative information about entities that are declared in your program. Everyone knows method or member modifiers as: private, public, protected and internal. These are attributes, standard attributes. Besides standard attributes, C# gives programmers an ability to create their own attributes. For example, you may add attribute ModelAttribute to your classes and describe in it how your class is presented in software model and design. Later, you can generate some documentation where your model is present by ModelAttribute to see if your model properly presented by classes and structures. Attributes information can be retrieved during runtime and this is very useful for specific scenarios. The main thing we should know about attributes is that it is metadata. Most of the attributes don't mean anything for the compiler and it ignores them and only stores them to metadata of assembly. .NET libraries have hundreds of attributes that you can use in your code. Few examples: DllImport, Conditional, Obsolete, Serializable, etc. these are attributes that you use in almost every project.

Attributes What Is Inside

First thing you should know and remember is that attribute is an object of a specific type. This type should directly or indirectly derive from System.Attribute abstract class. C# supports only CLS compatible attributes and to achieve this, you should derive your attribute type from System.Attribute. As attributes are objects of type, this type must have public constructor. Applying attributes is the same as creation of objects by calling constructor. One more additional thing that can be used when you declare attribute is a defining of open fields or properties or class. Syntax for applying attribute is:

[attribute(positional_parameters, name_parameter = value, ...)]

Name of the attribute and its values are specified within the square brackets, before the element to which the attribute is applied. Positional parameters specify the essential information and the name parameters specify the optional information. Positional parameters are passed to attribute via constructor and named parameters are every non static public read-write property or field. See example below:

[AttributeUsage(validon,AllowMultiple=allowmultiple,Inherited=inherited)]

You may apply more than one attribute for each element. You can put attributes in separate [] brackets, or specify one by one using comma in one brackets. Order of separate attributes and the one that go with comma is not relevant.

Attributes Example

In C#, names of custom attributes are stored in [] brackets. You should define attribute directly before name of the class, method, object, etc. Let's take a look at the code below, I explained in comments purpose of attributes that are used there:

//attribute DebuggerDisplay is used to show information about object of the
//class while debugging once I will move mouse pointer to this object I will
//see string that shows me the status of the object by sharing values of
//string_status and int1 member variables
[DebuggerDisplay("String status is={m_string_status}, second integer is: {m_int2}")]
internal class AttributesSampleClass
{
    private int m_int1 = 1;
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]//using this attribute 
            	//value of Int2 property will not be shown in debugger
     private int m_int2 = 2;
     private string m_string_status;

     [Obsolete("This method is obsolete, 
      use ConsolseOutput2 instead")]//having this attribute 
          	//we will have compiler warning when trying to use this method
      public void ConsoleOutput1()
      {
           Console.WriteLine("Output 1");
      }
      [DebuggerStepThrough]//this parameter says that debugger shouldn't step in this function
      public void ConsoleOutput2()
      {
          Console.WriteLine("Output 2");
      }
      public AttributesSampleClass() 
      {
      }
      //attribute flags indicates that numeration can be treated as a bit field
      [Flags]
      public enum EnumFlags 
      {
          Flag1 = 1,
          Flag2 = 2,
          Flag3 = 4,
          Flag4 = 8,
          Flag5 = 16,
          Flag6 = 32
      }
      [DebuggerNonUserCode]
      [DllImport("User32.dll")]// this attribute from System.Runtime.InteropServices gives
                                    // us ability to load SetForegroundWindow from User32.dll
      public static extern int SetForegroundWindow(IntPtr point);
}

     //SAMPLE
      Console.WriteLine("------------------SAMPLE---------------------");
      AttributesSampleClass sample = new AttributesSampleClass();
      sample.ConsoleOutput1();//when I call it like this i have warning 
      		//while building the project, but code executes
      sample.ConsoleOutput2();//works well

The screenshot below demonstrates how we hide value of integer member m_int2 from debugger, but showed it when moving mouse pointer to type. This was done using attributes for debugging:

<img alt="" src="1081990/1.png" style="width: 640px; height: 293px;" />

As you can see, attributes are very important and often used in day to day developer life. To be effective with .NET, you need first of all to know its attributes and their usage where it is required. Remember that C# allows you to apply attributes for everything that can be presented as metadata.

Predefined Attributes

The .NET Framework provides three pre-defined attributes:

  • AttributeUsage
  • Conditional
  • Obsolete

Creating Your Own Custom Attributes and Using Them

Now when we know about attributes, their usage and the fact that all of them derive from System.Attribute, let's do step by step creation of our own attribute:

You can analyze the following: Assembly, Module, ParameterInfo, MemberInfo, Type, MethodInfo, ConstructorInfo, FieldInfo, EventInfo, PropertyInfo and related to them *Builder. All of them have IsDefined and GetCustomAttributes.

  1. First of all, we need to define public class that is derived from System.Attribute. In my case, this class name is MyOwnExcitingAttribute. There is no requirement, but this is a recommendation from Microsoft to use suffix Attribute in your attribute type name.
  2. Second thing is that any Attribute class must have at least one open constructor. By adding parameters to constructor, you may specify there things that should provided by developer that use your attribute. Besides this, you can define non static open fields that developer may define or not.
  3. When you define your own Attribute class, you can use AttributeUsageAttribute to define area of usage for your attribute and it will be applied only for specific types or members. Besides this, you may configure other behavioral things for your own attribute using AttributeUsageAttribute:
    1. Attributes can be inherited if you use property Inherited=true in AttributeUsageAttribute.
    2. Most attributes can't be applied to the same element several times, and actually there is no reason to do this. If you want to apply the same attribute for entity more than one time, use named parameter AllowMultiple with true value.
  4. Defining attribute itself and applying it to some members is something useless. All you'll have finally is additional metadata in your assembly and there is no impact for your application workflow. This is only partially true. While runtime code is analyzed for attributes by mechanism called reflection. We will not dive into reflection in this article, but only review its usage. When you write your code that behaves differently if specific attributes are used, you should check for these attributes by yourself. There are several ways to check if attribute is defined. We will review two of them:
    1. You can call method IsDefined from type Type to check if some attribute is connected with type. If method IsDefined returns true, it means type is connected with the requested attribute:
      if (user.GetType().IsDefined(typeof(MyOwnExcitingAttribute), false))

      This is the code from my example. You can see the whole code in the attached archive with source. Please note that this method to check if attribute is defined, is applicable only for types.

    2. When we need to check if attribute is applied to method, assembly or module, we need to use another way than described above. For such purposes, we can use class System.Reflection.CustomAttributeExtensions. This class contains static methods to get attributes. In general, this class has the following three methods that has many overloads:
      • GetCustomAttribute - returns instance of requested attribute if it was applied for a specific module. This instance contains fields and values defined before compilation. If no attribute like this applied, it returns null.
      • GetCustomAttributes - returns array of instances of attributes of specific type. Same as previous each member has parameters that were assigned before compilation. Returns empty collection if nothing found.
      • IsDefined - returns true if some attributes of requested type were applied to a specific element. This is a very fast method as it does not reserialize anything and simply checks if attribute was applied or not.
  5. Recommendations for Attribute class:
    1. Try to use one constructor
    2. Try to avoid open members, rather properties
    3. Try to use simple types for attributes members and fields, don't use complex types or several dimensions arrays (you can read more about primitive types in my article here). This is a recommendation, you can use more complex members in your attribute class, but there is no guarantee it will be CLS compatible.
    4. Try not have methods in Attribute class - the idea for this class is to be simple and hold some state, try to avoid building complex logic inside Attribute class.
    5. Define attributes as sealed. Once you put the class to GetCustomAttributes, it will look for specific attribute or attributes derived from it. It may not return the exact class that you look for and you always need to check type of returned attribute. To avoid this checking, use sealed when you define attribute.

The code below demonstrates example for things described in this section:

[AttributeUsage(AttributeTargets.All,Inherited=false, AllowMultiple=false)]//I want 
     //my attribute to be applied to anything where it is possible - AttributeTargets.All
     //also my attribute can be inherited - Inherited=true
     //also my attribute can't be applied several times for one element - AllowMultiple=false
public sealed class MyOwnExcitingAttribute : System.Attribute
{
    private string m_StringData;
    private int m_IntegerData;
    
    public MyOwnExcitingAttribute()
    {
        m_StringData = "default value";
    }
    public MyOwnExcitingAttribute(string s)
    {
        m_StringData = s;
    }
    public int IntegerData
    {
        get { return m_IntegerData; }
        set { m_IntegerData = value; }
    }
    public string StringData
    {
        get { return m_StringData; }//we encapsulate only reading, 
        		//you can set this variable only in constructor
    }
}
public class AttributeUser
{
    [MyOwnExciting("custom value applied to function", IntegerData = 5)]
    public void FunctionThatDoSomething()
    {
    }
    [MyOwnExciting("custom value applied to member", IntegerData = 10)]
    public int m_IntData;
    [MyOwnExciting(IntegerData = 10)]//here string data will have "default value"
    public int m_IntData2;
}
public class NotAttributeUser
{
}

    //CUSTOM ATTRIBUTES
    AttributeUser user = new AttributeUser();
    //check if attribute is applied
    if (user.GetType().IsDefined(typeof(MyOwnExcitingAttribute), false))
    {
        Console.WriteLine("Attribute is defined");
    }
    NotAttributeUser not_user = new NotAttributeUser();
    //check if attribute is applied
    if (not_user.GetType().IsDefined(typeof(MyOwnExcitingAttribute), false))
    {
    
    }
    else 
    {
        Console.WriteLine("Attribute is not defined");
    }
    //reflection
    var members = from m in typeof(AttributesSampleClass).GetTypeInfo().DeclaredMembers select m;
    foreach (MemberInfo m in members)
    {
        ShowAttributes(m);
    }
    var members2 = from m2 in typeof(AttributeUser).GetTypeInfo().DeclaredMembers select m2;
    foreach (MemberInfo m2 in members2)
    {
        ShowAttributes(m2);
    }
}

public static void ShowAttributes(MemberInfo member)
{
    var attributes = member.GetCustomAttributes<Attribute>();//getting all attributes for specific member
    Console.WriteLine(member.Name + " attributes are:");
    foreach (Attribute at in attributes)
    {
        Console.WriteLine("Attribute type is: " + at.GetType().ToString());
        if (at is MyOwnExcitingAttribute)
        {
            Console.WriteLine("String data is: " + ((MyOwnExcitingAttribute)at).StringData);
            Console.WriteLine("int data is: " + ((MyOwnExcitingAttribute)at).IntegerData);
        }
    }
}

Some Useful Standard Attributes

  • [DebuggerDisplay] - Using it, you can display values of type members when moving mouse pointer to the type while debugging
  • [DebuggerBrowsable] - Using it, you can play with the way how member or property is shown in debugger
  • [DebuggerTypeProxy] - Used if you need to make significant changes to type under debugger but leaving original type unchanged
  • [DebuggerStepThrough] - Used for avoiding stepping into method under debugger
  • [DebuggerNonUserCode] - Indicates that type or member is not a user code of application
  • [Obsolete] - Used to notify developer that he shouldn't use this version of the code instead of the one he used. There is compilation warning about it.
  • [Conditional] - Is applied to method or class. It can be used to enable disable to display diagnostic information
  • [Flags] - Applicable to enums indicates that numeration can be treated as a bit field

You can find the full list of standard attributes here.

Sources

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here