
Reflection is a .NET framework functionality that allows us to inspect and manipulate the Metadata and the code compiled in a .NET assembly at runtime. It is a very powerful feature, but, as all the instruments at our disposal, we should understand how it works to use it properly
Assemblies provide essential information to recognize Types implementations to the Common Language Runtime. An assembly can be considered as a collection of types and resources, forming a logical unit of functionality, created to interact. They consist of one or more independent units, called Modules. Information important to us are contained in the main module, and they are:
- Assembly Manifest, that contains in turn the assembly name, the version and a catalog of all modules contained in it.
- Metadata, that contain a complete description of all displayed types, included methods, fields, parameters and constructors, any reference to external assemblies and the IL (Intermediate Language), which has been compiled from the source code and is the actual executable part of the assembly.
An assembly, compiled in IL (Intermediate Language), is compiled in machine language by the JIT compiler (Just in Time Compiler), also called JITter. The native code is produced when required, that’s to say, for example, the first time a method is invoked, it will be compiled and saved. It will be available in the cache for next calls and then we save the compiling time. When not used, assemblies would not be loaded. This means that they represent an effective manner to manage resources in big projects. The benefit of JIT compilation is that it could be done dynamically, in order to take advantage of the underlying system.
Using Metadata we can dynamically create instances of Types, we can invoke methods, obtain and set properties and fields values or inspection attributes.
C# provides us classes and methods to execute these and much else, using the so-called Reflection.
Classes that much interest us for the use of Reflection are three:
Type Class
The class type is an abstract class, that would be used to access to Metadata through the Reflection classes. Using a Type object, it is possible to obtain information related to a class such as constructors, methods, fields, properties and events.
Activator Class
This class contains methods used to dynamically create an instance of a Type.
Assembly Class
This abstract class allows us to load, manipulate and obtain information on an assembly and to identify all Types contained in it.
Let’s look at a practical example, using the Reflection and personalized attributes to map objects’ properties.
Although Entity Framework and the namespace System.ComponentModel.DataAnnotations already provide us with tools to make it, we may need to build our level or our tool to access data.
We can implement our version of these data annotations as Personalized Attributes. To create a personalized attribute in C#, we simply create a class, that inherits from System.Attribute. For example, if we want to implement our attribute [PrimaryKey] to indicate that a specific property on a class of our application represents the primary key of our database, we can create the following personalized attribute:
public class PrimaryKeyAttribute : Attribute { }
Attributes can even transmit information if needed. Let’s consider a way to map a property on a specific database column name.
Then we create a class, that inherits from System.Attribute, but this time we will add a property and a constructor
public class DbColumnAttribute : Attribute
{
public string Name { get; set; }
public DbColumnAttribute(string name)
{
this.Name = name;
}
}
Now suppose we inherit a database that should be integrated with our existing code. In the table containing all information about the client, the names of columns are lowercase and separated by an underscore, to better understand the context. Shown below, the SQL code of the creation on the mentioned table:
CREATE TABLE Users (
id_user int IDENTITY(1,1) PRIMARY KEY NOT NULL,
first_name varchar(50) NOT NULL,
last_name varchar(50) NOT NULL,
email varchar(50) NOT NULL
);
And this is how our User class will appear with column name attributes:
public class User
{
[PrimaryKey]
[DbColumn("id_user")]
public int UserId { get; set; }
[DbColumn("first_name")]
public string Name { get; set; }
[DbColumn("last_name")]
public string Surname { get; set; }
[DbColumn("email")]
public string Email { get; set; }
}
We can now write a method that allows us to map properties of our class on the specific column name of the database, using Reflection:
static void DbColumnsMappingMethod<T> (T obj) where T : new()
{
// Get the istance's Type object
Type type = obj.GetType();
// Get a list of the public properties of the Type object as PropertyInfo objects
PropertyInfo[] objectPropertiesList = type.GetProperties();
Console.WriteLine("Search properties for the object: {0}", type.Name);
foreach (var objectProperty in objectPropertiesList)
{
// Get custom attributes of object's properties
var customAttributes = objectProperty.GetCustomAttributes(false);
string message = "The property {0} will be mapped with the database column {1}";
// Mapping custom attributes with DB columns
var mappedColumn = customAttributes
.FirstOrDefault(a => a.GetType() == typeof(DbColumnAttribute));
if (mappedColumn != null)
{
DbColumnAttribute dbColumn = mappedColumn as DbColumnAttribute;
Console.WriteLine(message, objectProperty.Name, dbColumn.Name);
}
}
}
We can even write a method that analyzes properties and attributes of an object and search for its primary key, using reflection:
static void PrimaryKeySearchMethod<T> (T obj) where T : new()
{
// Get the istance's Type object
Type type = obj.GetType();
// Get a list of the public properties of the Type object as PropertyInfo objects
PropertyInfo[] objectPropertiesList = type.GetProperties();
Console.WriteLine("Search Primary Key for the object: {0}", type.Name);
// Primary Key search
var primaryKey = objectPropertiesList
.FirstOrDefault(p => p.GetCustomAttributes(false)
.Any(a => a.GetType() == typeof(PrimaryKeyAttribute)));
if (primaryKey != null)
{
string message = "The Primary Key for {0} class is the property: {1}";
Console.WriteLine(message, type.Name, primaryKey.Name);
}
}
In the code above, we pass to the method a generic object of T type, which means that this method can be used with any domain object, to verify the existence of an attribute. [PrimaryKey].
First of all, we can use the method GetType() to find information about the object Type, then we call the method GetProperties() of the Type instance, which returns an array of PropertyInfo objects.
Successively, we execute the iteration using LINQ on every PropertyInfo instances and we call the method GetCustomAttributes(), which returns an object matrix, that represents CustomAttributes found on that property. We can now check the type of every CustomAttributes object and, if it is of PrimaryKeyAttribute type, we know that we have found a property that represents a primary key in our database.
We should specify that the use of Reflection may cause a worse performing of our code, but thanks to the power of today’s processors, this problem would be noted only when the code is used repeatedly in a great application context. We can weaken this problem with the use of abstractions instead of concrete Types, a best practice that usually results in the use of an interface instead of a concrete class. Using an interface, we can find that most of our code interacts with the abstraction, instead of with the dynamically created Type.
If needed, we can dynamically load an assembly only when the application starts; then dynamically load Types contained in it, verifying they have been loaded only once: as a Type has been obtained, we can use it using an interface that our application will use to make all calls to methods: in this way, there will be no dynamic calls through MethodInfo and the Invoke method.
This simple strategy example will help us to minimize the decline in performance and, since interfaces only have public members, we avoid interacting with private members of the class. The result would be that we will delimit the use of Reflection only to those parts of the application we need it, maximizing PERFORMANCES and keeping the FLEXIBILITY we need.
See you next!