A .NET application doesn’t just contain code and data; it also contains metadata, which is information about data. In .NET, this means that an application contains the code that defines the application and data that describes the code.

An attribute is one type of metadata that can be stored in a .NET application. Other types of metadata contain information about the types, code, assembly, and all other elements stored in your application.

Reflection is the process of retrieving this metadata at runtime. The data can be inspected and used to make decisions.

In this article, you will learn how to use C# attributes in your own code and how to use reflection along with attributes.

Contents

Using C# Attributes

Using attributes is a powerful way to add metadata to an application. Attributes can be added to all kinds of types: assemblies, types, methods, parameters, and properties.

We can make use of attributes at code time, at design time, and at run time.

C# Code Time Attributes

So, for example, we can use attributes during coding to further describe the code that we’re writing. At code time, we can also make use of attributes to control the compilation of our code. For example, we can use attributes to configure conditional compilation.

We can also use attributes to control the debugging experience in Visual Studio. Some of the attributes that we’ll look at include this conditional compilation and debugging attributes.

C# Design Time Attributes

Attributes can be used at design time, for example, if we’re making use of a visual GUI designer. When we drag and drop a button to a new position, this new position could be represented as attributes in some kind of code-behind.

If we’re making use of an integrated development environment, such as Visual Studio, when we make changes to properties in Visual Studio, these changes may be represented as attributes in our code base.

C# Run Time Attributes

Attributes can also provide information at run time. So, for example, if we’re making use of ASP. NET, we might use attributes to define data validation.

At run time, reflection can be used to access attributes that have been applied to code elements and use those attributes and any properties set on those attributes to control the execution of the program at run time.

Applying C# Attributes

How to apply a C# attribute

In C#, you apply an attribute by placing the attribute name in square brackets [] above the declaration that you want the attribute to apply to.

One example of an attribute in the .NET Framework is SerializableAttribute . This attribute indicates that a type can be serialized. The .NET Framework checks for the existence of this attribute when serializing a type, and it makes sure that all members of the type can also be serialized.

The below example shows how to apply the Serializable attribute.

1 [Serializable] 2 class Person 3 { 4 public string FirstName { get ; set ; } 5 public string LastName { get ; set ; } 6 }

As you can see, the actual class in the .NET Framework is called SerializableAttribute. By convention, the name is suffixed with Attribute so you can easily distinguish between attributes and other types in the .NET Framework. When using the attribute, however, you can skip the Attribute suffix.

Using multiple C# attributes

A type can have as many attributes applied to it as necessary. Some attributes can even be applied multiple times. For example, you can use the ConditionalAttribute to indicate to the compiler that a method call should be ignored unless a specific compiler option is specified. The below example shows how to apply this attribute.

1 [Conditional( "CONDITION1"), Conditional( "CONDITION2")] 2 static void MyMethod (){ }

An attribute can have parameters. Just as with regular types, those parameters can be named an optional. The values set to an attribute can later be inspected at runtime.

Specifying the target of a C# attribute explicitly

An attribute also has a specific target to which it applies. It can be an attribute applied to a whole assembly, a class, a specific method, or even a parameter of a method.

If you look at the AssemblyInfo.cs of a new class library, you can see how the target is explicitly specified.

1 [assembly: AssemblyTitle( "ClassLibrary1")] 2 [assembly: AssemblyDescription( "")] 3 [assembly: AssemblyConfiguration( "")] 4 [assembly: AssemblyCompany( "")] 5 [assembly: AssemblyProduct( "ClassLibrary1")] 6 [assembly: AssemblyCopyright( "Copyright © 2013")] 7 [assembly: AssemblyTrademark( "")] 8 [assembly: AssemblyCulture( "")]

These attributes are all applied to the current assembly and describe some metadata about the assembly.

Prebuilt C# Attributes

DebuggerDisplay attribute

The DebuggerDisplay attribute is available to customize the display of Class, Struct, Delegate, Enum, Field, Property, and Assembly.

Let’s consider the following Person class, which has two properties as FirstName and LastName.

1 public class Person 2 { 3 public string FirstName { get ; set ; } 4 public string LastName { get ; set ; } 5 }

The normal way the class and its properties displayed is as follows.

To add the DebuggerDisplay attribute, you need to refer to System.Diagnostics namespace.

The attribute takes a string value as a parameter.

The following code shows that the Properties are used as parameters enclosed in curly braces.

1 [DebuggerDisplay("Hello {FirstName} {LastName}!")] 2 public class Person 3 { 4 public string FirstName { get ; set ; } 5 public string LastName { get ; set ; } 6 }

The display of the class would change like the following.

DebuggerBrowsable attribute

DebuggerBrowsable is an attribute available to C# language. This attribute is helpful for determining the display of properties and fields.

Let’s consider the following Person class, which has two properties as FirstName and LastName.

1 public class Person 2 { 3 public string FirstName { get ; set ; } 4 5 public string LastName { get ; set ; } 6 7 public int [] WorkingDays { get ; set ; } 8 }

Debugging the code displays the usual way of the array name and its values on expanding.

Let’s add the attribute. You need to add a reference to System.Diagnostics namespace.

Notice that, it takes an enum as a parameter. The DebuggerBrowsableState enum has the following values.

Using this attribute other than Property, Index, and Field would give you the error. Now we would add the RootHidden enum type of DebuggerBrowsableState to AbsentDays property.

1 public class Person 2 { 3 public string FirstName { get ; set ; } 4 5 public string LastName { get ; set ; } 6 [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 7 public int [] WorkingDays { get ; set ; } 8 }

Notice that, the property name is not displayed and the children are expanded by default.

This is again very useful if you have many properties and you need to check a few of them. It saves time to drill down.

Conditional attribute

A conditional attribute is a tag used to mark a method or class whose execution depends on the definition of preprocessing identifier.

If specific symbols have not been defined at the instant they are called, then calls to that method or class will be ignored by the compiler.

Let’s consider the following Program class, which has a Display person method.

1 class Program 2 { 3 public class Person 4 { 5 public string FirstName { get ; set ; } 6 7 public string LastName { get ; set ; } 8 [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 9 public int [] WorkingDays { get ; set ; } 10 } 11 12 static void Main ( string [] args ) 13 { 14 var person = new Person 15 { 16 FirstName = "Shadman" , 17 LastName = "Kudchikar" , 18 WorkingDays = new int [] { 1 , 2 , 3 , 4 } 19 }; 20 Display ( person ); 21 22 ReadLine (); 23 } 24 25 public static void Display ( Person person ) 26 { 27 WriteLine ( person . FirstName ); 28 } 29 }

The conditional attribute takes conditionString as a parameter. Wherein methods are selectively called on the basis of this definition of the symbol.

It instructs the compiler to compile (into Microsoft Intermediate Language) or not, based on a condition - whether or not a specific conditional compilation symbol is defined.

1 [Conditional("Release")] 2 public static void Display ( Person person ) 3 { 4 WriteLine ( person . FirstName ); 5 }

Now if we run the program it will not display any output. However when we can define the symbol in project properties.

Now if we run the program we can see the output

Reading C# attributes

Applying an attribute isn’t that useful if you can’t retrieve it. Luckily, the .NET Framework offers support for reading attributes through a process called reflection.

The System.Attribute class, from which all other attributes inherit, defines some static methods that can be used to see whether an attribute is applied and to get the current instance of an attribute so you can further inspect it.

Suppose that you want to check that a class has the Serializable attribute applied. You can do this by calling the static IsDefined method on Attribute.

1 [Serializable] class Person { }

1 if ( Attribute . IsDefined ( typeof ( Person ), typeof ( SerializableAttribute ))) 2 { 3 //do something 4 }

C# Reflection

When reading attributes, you’ve already looked at some of the functionality that reflection offers. Reflection enables an application to collect information about itself and act on this information. Reflection is slower than normally executing static code. It can, however, give you a flexibility that static code can’t provide.

The most basic example of reflection is getting the current type of an object you have:

1 int i = 42 ; 2 Type type = i . GetType ();

This returns System.Int32 as the type of int. Type is a class in the .NET Framework that you can use to get all kinds of metadata about any given type.

By using Reflection in C#, one is able to find out details of an object, method, and create objects and invoke methods at runtime.

Let’s create a class and invoke its method using reflection.

Invoke Method with parameters using C# reflection

1 public class MyClass 2 { 3 public virtual int AddNumb ( int numb1 , int numb2 ) 4 { 5 int result = numb1 + numb2 ; 6 return result ; 7 } 8 9 }

The above class has the AddNumb method which takes two int parameters and returns their addition.

You can call this method using reflection like this:

1 class MyMainClass 2 { 3 public static void Main () 4 { 5 // Create MyClass object 6 MyClass myClassObj = new MyClass (); 7 8 // Get the Type information. 9 Type myTypeObj = myClassObj . GetType (); 10 11 // Get Method Information. 12 MethodInfo myMethodInfo = myTypeObj . GetMethod ( "AddNumb" ); 13 14 object [] mParam = new object [] { 5 , 10 }; 15 // Get and display the Invoke method. 16 Console . Write ( "

First method - " + myTypeObj . FullName + " returns " + 17 myMethodInfo . Invoke ( myClassObj , mParam ) + "

" ); 18 } 19 }

So this is how it work first you get the type information:

1 Type myTypeObj = myClassObj . GetType ();

Then will get the method’s information:

1 Methodinfo myMethodInfo = myTypeObj . GetMethod ( "AddNumb" );

Finally, we will invoke the AddNumb method:

1 myMethodInfo . Invoke ( myClassObj , mParam );

Things you can achieve with C# reflection

You can use reflection to configure your application by loading an external configuration file and starting services based on it. Your application won’t have to know in advance about the classes that implement those services, as long as they conform to a specific interface or API.

Using reflection you can generate classes and code on the fly, which simplifies certain programming tasks since the programmer does not have to explicitly create all the needed code. For example, See Applying C# Reflection with Attributes.

Reflection is also invaluable for programs that work by examining code. An example of that would be an IDE or a UI designer.

Reflection helps you reduce boilerplate code.

Reflection is handy for defining mini Domain-Specific Languages (DSL) in your code.

Applying C# Reflection with Attributes

Let’s see a problem statement that a typical C# programmer will face converting a datatable into C# user-defined types

For this example, I am creating a simple Student class.

1 public class Student 2 { 3 public int StudentId { get ; set ; } 4 public string StudentName { get ; set ; } 5 public string Address { get ; set ; } 6 public string MobileNo { get ; set ; } 7 }

And a DataTable with some data.

1 DataTable dt = new DataTable ( "Student" ); 2 dt . Columns . Add ( "StudentId" , typeof ( Int32 )); 3 dt . Columns . Add ( "StudentName" , typeof ( string )); 4 dt . Columns . Add ( "Address" , typeof ( string )); 5 dt . Columns . Add ( "MobileNo" , typeof ( string )); 6 //Data 7 dt . Rows . Add ( 1 , "Manish" , "Hyderabad" , "0000000000" ); 8 dt . Rows . Add ( 2 , "Venkat" , "Hyderabad" , "111111111" ); 9 dt . Rows . Add ( 3 , "Namit" , "Pune" , "1222222222" ); 10 dt . Rows . Add ( 4 , "Abhinav" , "Bhagalpur" , "3333333333" );

Now, I will convert the preceding DataTable into a List<Student> using Linq.

1 List < Student > studentList = new List < Student >(); 2 studentList = ( from DataRow dr in dt . Rows 3 select new Student () 4 { 5 StudentId = Convert . ToInt32 ( dr [ "StudentId" ]), 6 StudentName = dr [ "StudentName" ]. ToString (), 7 Address = dr [ "Address" ]. ToString (), 8 MobileNo = dr [ "MobileNo" ]. ToString () 9 }). ToList ();

The above solution works well for the Student class but what if we have more datatables like this, we can have classes for Teachers, Employees, and whatnot. In that case, we have to write the mapping code just like we did for the Student class in the above example.

However, we can use reflection to write a code that converts datatable to user-defined types based on column names and class properties. Let’s see how to achieve this,

Instantiate a type dynamically using reflection

The following are the two functions in which if we pass a DataTable and a user-defined class as a generic parameter it will then return the List of that class with the DataTable data.

1 private static List < T > ConvertDataTable < T >( DataTable dt ) 2 { 3 List < T > data = new List < T >(); 4 foreach ( DataRow row in dt . Rows ) 5 { 6 T item = GetItem < T >( row ); 7 data . Add ( item ); 8 } 9 return data ; 10 } 11 12 private static T GetItem < T >( DataRow dr ) 13 { 14 // Get the Type information. 15 Type temp = typeof ( T ); 16 // Create object of type T 17 T obj = Activator . CreateInstance < T >(); 18 19 foreach ( DataColumn column in dr . Table . Columns ) 20 { 21 //Get the Properties information 22 foreach ( PropertyInfo pro in temp . GetProperties ()) 23 { 24 if ( pro . Name == column . ColumnName ) 25 pro . SetValue ( obj , dr [ column . ColumnName ], null ); //Set the property of object 26 else 27 continue ; 28 } 29 } 30 return obj ; 31 }

Now, we can convert the preceding DataTable like this,

1 List < Student > studentDetails = ConvertDataTable < Student >( dt );

Thus, reflection simplifies the instantiating and mapping task. The programmer does not have to explicitly write all the code to create an object and setting it values that we did previously using LINQ.

Notice, how we used the typeof operator to get the type of T from a generic method.

If we call the method like this, ConvertDataTable<Student>(...) our type will be Student and If we used, ConvertDataTable<Teacher>(...) our type will be Teacher.

You may have noticed the following line of code as well

1 if ( pro . Name == column . ColumnName ) 2 pro . SetValue ( obj , dr [ column . ColumnName ], null );

The above condition checks if the property name of the user-defined class is the same as datatable on and set that property value based on the matched column name.

Thus,

The DataTable column’s name and class property name should be the same otherwise this function will not work properly.

Now, what if these names somehow failed to match and you don’t want to rename the class properties as well

1 public class Student 2 { 3 public int Id { get ; set ; } //Id for student 4 public string StudentName { get ; set ; } 5 public string Address { get ; set ; } 6 public string MobileNo { get ; set ; } 7 }

1 DataTable dt = new DataTable ( "Student" ); 2 dt . Columns . Add ( "StudentId" , typeof ( Int32 )); //Id for student 3 dt . Columns . Add ( "StudentName" , typeof ( string )); 4 dt . Columns . Add ( "Address" , typeof ( string )); 5 dt . Columns . Add ( "MobileNo" , typeof ( string ));

As you can see Id property in Student class and Datatable is mismatched.

Let’s tackle the above problem using attributes

Creating custom C# attributes

First, we will define a custom attribute NamesAttribute .

1 public class NamesAttribute : Attribute 2 { 3 public string [] Values { get ; set ; } 4 }

It only has one property Values which is a string array. Now we will use this attribute on Id property in our Student class like this:

1 public class Student 2 { 3 [Names(Values = new string[] { "StudentId" })] 4 public int Id { get ; set ; } 5 public string StudentName { get ; set ; } 6 public string Address { get ; set ; } 7 public string MobileNo { get ; set ; } 8 }

Using reflection to read C# attribute

Now we will write a method that checks whether Values defined on a property match with columnName passed in the parameter, using reflection.

1 private static bool NamesMatched ( PropertyInfo pro , string columnName ) 2 { 3 var displayNameAttr = pro . GetCustomAttributes ( false ) 4 . OfType < NamesAttribute >(). FirstOrDefault (); 5 return displayNameAttr ?. Values . Any ( v => v == columnName ) ?? false ; 6 }

Now we can update our GetItem method to use the above method like this

1 private static T GetItem < T >( DataRow dr ) 2 { 3 Type temp = typeof ( T ); 4 T obj = Activator . CreateInstance < T >(); 5 6 foreach ( DataColumn column in dr . Table . Columns ) 7 { 8 foreach ( PropertyInfo pro in temp . GetProperties ()) 9 { 10 var isNameMatched = NamesMatched ( pro , column . ColumnName ); 11 if ( pro . Name == column . ColumnName || isNameMatched ) 12 pro . SetValue ( obj , dr [ column . ColumnName ], null ); 13 else 14 continue ; 15 } 16 } 17 return obj ; 18 }

Now our program will able to map the properties values even though the name of columns and properties mismatched. This was possible as it takes into consideration the metadata that we attached with the property using attributes.

That’s all for now, join me in the next article by subscribing to my newsletter using the form below.

Further Reading