Click here to Skip to main content
15,885,067 members
Articles / Web Development / ASP.NET

Reflection with Example

Rate me:
Please Sign up or sign in to vote.
4.80/5 (50 votes)
18 Feb 2015CPOL8 min read 70.3K   1.4K   104   15
Process of runtime type discovery is called reflection. Using reflection, we are able to obtain the metadata information, dynamically.

Introduction

The process of runtime type discovery is called reflection. Using reflection, we are able to obtain the metadata information, dynamically. For example, we can get a list of all types contained within a given assembly, including methods, attributes, fields, custom attributes, properties and many more.

System.Type

Before diving into reflection, first have a look at System.Type class because it is a base foundation of reflection API.

System.Type class defines many members that can be used to explore a type’s metadata information. Many of its member return type is from System.Reflection namespace. For example, GetProperties() returns a PropertyInfo[], GetFields() returns FieldInfo[] and so on.

System.Type contains properties like IsAbstarct, IsArray, IsClass, IsCOMObject, IsValueType, IsInterface, IsPrimitive, IsEnum and the list goes on, which allows to find basic features about the Type in context, for example if it is an abstract class, an array and so on.

It also contains methods like GetMethods(), GetProperties(), GetNestedTypes(), GetInterfaces(), GetEvents(), GetConstructors(), etc., allow to get an array representing the items (methods, property, interfaces, etc.) we are exploring.

Notes: Each of these method has a singular form, for example, GetMethod(),GetProperty(), etc., that allows to get a specific item by name.

System.Reflection

This namespace contains types that retrieve information about assemblies, modules, members, parameters and other entity. These can also be used to manipulate instance of loaded types, for example invoking methods, etc.

Brief description of some member of System.Reflection namespace:

  • Assembly: An abstract class that contains many static methods that allow to load, investigate and manipulate an assembly.
  • AssemblyName: This class allows to discover more details for an assembly for e.g. version Info, culture info, etc.
  • EventInfo: An abstract class holds information for a given event.
  • FieldInfo: An abstract class holds information for a given field.
  • MemberInfo: This abstract class that defines common behavior for the EventInfo, FieldInfo, MethodInfo and PropertyTypeInfo.
  • MethodInfo: An abstract class holds information for a given method.
  • Module: An abstract class that allows to access a given module within a multifile assembly.
  • ParameterInfo: An abstract class holds information for a given parameter.
  • PropertyInfo: An abstract class holds information for a given property.

Get Type Reference

We can get an instance of Type class in various ways.

Using System.Object.GetType()

System.Object defines a method named GetType(), which returns an instance of the Type class that represents the metadata for the current object.

C#
// Obtain type information using a Contact instance.
Contact contact = new Contact();
Type t = contact.GetType();

This approach will work only if we have compile-time knowledge of the type which we want to explore and currently have an instance of the type in memory.

Using typeof()

The other way is to obtain type metadata is using the typeof operator:

C#
// Get the type using typeof.
Type t = typeof(Contact);

This approach doesn’t require to create an object instance to type information.

Using System.Type.GetType()

Type.GetType() provides type information in a more flexible manner. Using this method, we do not need to have compile time knowledge of the type, since we specify the fully qualified string name of the type that is to be explored.

The Type.GetType() method has been overloaded to specify two Boolean parameters, one of which controls whether an exception should be thrown if the type cannot be found, and the other of which checks the case sensitivity of the string.

C#
// Obtain type information using the static Type.GetType() method
// (don't throw an exception if 'Contact' cannot be found and ignore case).
Type t = Type.GetType("UnderstandingReflection.Contact", false, true);

In this code, the assumption is that the type is defined within the currently executing assembly. However, when we want to obtain metadata for a type within an external assembly, the string parameter is formatted using the type’s fully qualified name, followed by a comma, followed by the friendly name of the assembly containing the type:

C#
// obtain type information for a type within an external assembly.
Type t = Type.GetType("UnderstandingReflection.Contact, ContactLib");

In Type.GetType() method, we can also specify a plus token (+) to denote a nested type. Suppose we want to get type information for a class (ClassName) which is nested within a class named {Class_Name}.

C#
// Obtain type information for a nested class
// within the current assembly.
Type t = Type.GetType("UnderstandingReflection.Contact+ContactOption");

Getting Methods Information

C#
/// <summary>
/// To print all method with its return type and parameter info
/// </summary>
/// <param name="t">type to be reflect</param>
static void ListofMethodsWithParams(Type t)
{
    Console.WriteLine("\n***** Methods *****\n");
    MethodInfo[] methodInfos = t.GetMethods();//get all method of current type
    StringBuilder paramInfo = new StringBuilder();
    foreach (MethodInfo methodInfo in methodInfos)
    {
        // Get return type.
        string retVal = methodInfo.ReturnType.FullName;
        paramInfo.Append("( ");
        // Get params.
        foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
        {
            paramInfo.Append(string.Format("{0} {1} ", parameterInfo.ParameterType, parameterInfo.Name));
        }
        paramInfo.Append(" )");
        // Now display the basic method signature.
        Console.WriteLine(String.Format("{0} {1} {2}\n", retVal, methodInfo.Name, paramInfo.ToString()));
        paramInfo.Clear();
    }
}

In this method, a parameter is passed of type Type. From this type, to get all method GetMethod() is called that return an array of MethodInfo instance. Now iterating through all methodInfo, we can get return type of that method using methodInfo.ReturnType.FullName. Now to get all parameters of that method, we call methodInfo.GetParameters(). From parameterInfo, we get parameter type and its name.

Getting Field Information

C#
/// <summary>
/// To print list of field with its type
/// </summary>
/// <param name="type">type to be reflect</param>
static void ListOfField(Type type)
{
    Console.WriteLine("***********Field************");
    //getting all field
    FieldInfo[] fieldInfos = type.GetFields();
    foreach (var fieldInfo in fieldInfos)
    {
        Console.WriteLine(String.Format
        ("{0}  {1}\n", fieldInfo.FieldType.Name, fieldInfo.Name));
    }
}

To get all fields, I am using GetFields() that returns a FieldInfo[] object, and from fieldInfo object, we can get its information.

In this method, I am using BindingFlags(which is an enum) that controls binding and the way in which the exploring for members and types is used by reflection. This provides a greater level of control on exactly what we are searching for (e.g., only static members, only public members, include private members, etc.).

Some Binding flags are:

  • BindingFlags.Public: Specifies that public members are to be included in the search
  • BindingFlags.Instance: Specifies that instance members are to be included in the search
  • BindingFlags.NonPublic: Specifies that non-public members are to be included in the search

Getting Property Information

C#
/// <summary>
/// To print list of property with its type
/// </summary>
/// <param name="type">type to be explore</param>
static void ListOfProperty(Type type)
{
    Console.WriteLine("***********Property************");
    //getting all property
    PropertyInfo[] propertyInfos = type.GetProperties();
    foreach (var propertyInfo in propertyInfos)
    {
        Console.WriteLine(String.Format
        ("{0}  {1}\n", propertyInfo.PropertyType.Name, propertyInfo.Name));
    }
}

Getting Some Various Statistics

Last but not least, you have one final helper method that will simply display various statistics (indicating whether the type is generic, what the base class is, whether the type is sealed, and so forth) regarding the incoming type.

C#
// Just for Understanding
private void ListVariousStats(Type t)
{
    Console.WriteLine( "\n***** Various Statistics *****\n");
    Console.WriteLine( String.Format("Base class is: {0}\n", t.BaseType));
    Console.WriteLine( String.Format("Is type sealed? {0}\n", t.IsSealed));
    Console.WriteLine( String.Format("Is type generic? {0}\n", t.IsGenericTypeDefinition));
    Console.WriteLine(String.Format("Is type a class type? {0}\n", t.IsClass));
}

Example Application

Example Application is web application to provide use of reflection by creating instance at run time, basic database operation (CRUD operation and creation of stored procedure), etc.

So the idea is perform Crud operation which requires to have stored procedure in database, and passing SQL parameter when calling the stored procedure. To demonstrate reflection in C# application, I am doing all operations through reflection API like creating instance of repository, creating stored procedure, creating SqlParameter, converting DataTable to List<T>, etc.

Firstly, I created custom attributes for class property that represent my database table, to identify Identity column and general column and to mapping class to table.

I use this attribute for mapping table name to class.

C#
[AttributeUsage(AttributeTargets.Class,AllowMultiple=false)]
public class TableNameAttribute : Attribute
{
    public string Name { get; set; }
    public TableNameAttribute(string name)
    {
        this.Name = name;
    }
}

This attribute is applied on class property, to represent that this property is identity column in database.

C#
[AttributeUsage(AttributeTargets.Property,AllowMultiple=false)]
public class IdentityAttribute:Attribute
{
    public bool IsID { get; set; }
    public IdentityAttribute()
    {
        this.IsID = true;
    }
}

This attribute is used to represent corresponding column name in table.

C#
[AttributeUsage(AttributeTargets.Property,AllowMultiple=false)]
public class DbColumnAttribute:Attribute
{
    public string Name { get; set; }
    public DbColumnAttribute(string name)
    {
        this.Name = name;
    }
}

This is how I used these attribute in my model class:

C#
[TableName("ContactDetails")]
public class Contact:IEntity
{
    #region IEntity Members
    [Identity]
    [DbColumn("ID")]
    public int ID { get; set; }

    [DbColumn("GUID")]
    public string GUID { get; set; }
    #endregion

    [DbColumn("PersonName")]
    public string Name { get; set; }

    [DbColumn("Address")]
    public string Address { get; set; }

    [DbColumn("EmailId")]
    public string Email { get; set; }
}

Getting List of SqlParameter

In this method, an instance of object in received who have to get the SqlParameter to pass the stored procedure. Firstly, I get the all public and non-static property and iterating through all property info, I add it to SqlParameter list.

C#
private List<SqlParameter> GetAddParameter(object obj)
{
    PropertyInfo[] fields = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    var sqlParams = new List<SqlParameter>();
    foreach (var f in fields)
    {
        var cols = f.GetCustomAttributes(typeof(DbColumnAttribute), false) ;
        if (cols.Length > 0 && 
        f.GetCustomAttributes(typeof(IdentityAttribute), false).Length == 0)
        {
            var p = cols[0];
            sqlParams.Add(new SqlParameter(((DbColumnAttribute)p).Name, f.GetValue(obj, null)));
        }
    }
    return sqlParams;
}

Get Table Name

Getting table to use in procedure for an object, I created this extension method.

C#
public static string GetTableName<T>(this T entity) where T : IEntity
{
    //Getting TableNAmeAtrribute from entity
    var tableNameAttr = entity.GetType().GetCustomAttributes(typeof(TableNameAttribute), false);
    //If not found the TableNameAttribute raise an exception
    if (tableNameAttr.Length == 0)
        throw new ArgumentException(String.Format("{0} 
        Class has not been mapped to Database table with 'TableNameAttribute'.", 
        entity.GetType().Name));

    //returning Table Name
    return ((TableNameAttribute)tableNameAttr[0]).Name;
}

To Create Stored Procedure

I have created stored procedure for database operation, by calling:

C#
public static bool CreateAddEntityProcedure(object entity, SqlConnection con)
{
    string tableColumnName = "", tableColNameForValue = "", 
    procedureParameter = "", spname = "";
    string tableName = GetTableName(entity);

   #region Building procedure
    procedureParameter = CreateParameter(entity, out tableColumnName, out tableColNameForValue);

    #region
    spname = "Add" + tableName;
    string spCreate = @"           
-- =============================================
-- Author:      Demo Reflection
-- Create date: {0}
-- Description: CREATING New Row in {1}
-- =============================================
CREATE PROCEDURE {2}
{3}
AS
BEGIN
    INSERT INTO {4}
    ({5})
    values
    ({6})
    SELECT SCOPE_IDENTITY()
END";
            #endregion

            #endregion
    string addProcedureDetails = String.Format(spCreate, DateTime.Now,
        tableName, spname, procedureParameter, tableName, tableColumnName, tableColNameForValue);
    return StoringProcedure(addProcedureDetails, con);
}

I created CreateParameter method for programmatically creating a stored procedure for entity. In this method, object for whose we want to create procedure should be decorated with attribute like, TableNameAttribute, etc. for getting table name, and getting column name and its types.

C#
private static string CreateParameter(object entity, out string tableColName, out string tableValueParam)
{
  if (!(entity is IEntity))
  {
    throw new ArgumentException("Invalid Entity to create Procedure");
  }

  StringBuilder parameter = new StringBuilder();
  StringBuilder TableColumnName = new StringBuilder();
  StringBuilder paramForValue = new StringBuilder();
  //Getting the type of entity
  var type = entity.GetType();

  // Get the PropertyInfo object:
  var properties = type.GetProperties();
  foreach (var property in properties)
  {
    if (property.GetCustomAttributes(typeof(IdentityAttribute), false).Length > 0)
      continue;

    var attributes = property.GetCustomAttributes(typeof(DbColumnAttribute), false);
    foreach (var attribute in attributes)
    {
      if (attribute.GetType() == typeof(DbColumnAttribute))
      {
        switch (property.PropertyType.FullName)
        {
          case "System.Int32":
            TableColumnName.Append(String.Format("{0},", ((DbColumnAttribute)attribute).Name));
            paramForValue.Append(String.Format("@{0},", ((DbColumnAttribute)attribute).Name));
            parameter.AppendLine();
            parameter.Append(String.Format("@{0} int=0,", ((DbColumnAttribute)attribute).Name));
          break;
          case "System.Int64":
            TableColumnName.Append(String.Format("{0},", ((DbColumnAttribute)attribute).Name));
            paramForValue.Append(String.Format("@{0},", ((DbColumnAttribute)attribute).Name));
            parameter.AppendLine();
            parameter.Append(String.Format("@{0} bigint=0,", ((DbColumnAttribute)attribute).Name));
          break;
          case "System.DateTime":
            TableColumnName.Append(String.Format("{0},", ((DbColumnAttribute)attribute).Name));
            paramForValue.Append(String.Format("@{0},", ((DbColumnAttribute)attribute).Name));
            parameter.AppendLine();
            parameter.Append(String.Format("@{0} datetime=1/1/0001,", ((DbColumnAttribute)attribute).Name));
          break;
          default:
            TableColumnName.Append(String.Format("{0},", ((DbColumnAttribute)attribute).Name));
            paramForValue.Append(String.Format("@{0},", ((DbColumnAttribute)attribute).Name));
            parameter.AppendLine();
            parameter.Append(String.Format("@{0} varchar(max)='',", ((DbColumnAttribute)attribute).Name));
          break;

        }
      }
    }
  }
  GetTableName(entity);
  var allParams = parameter.ToString().Substring(0, parameter.ToString().LastIndexOf(","));
  tableColName = TableColumnName.ToString().Substring(0, TableColumnName.ToString().LastIndexOf(","));
  tableValueParam = paramForValue.ToString().Substring(0, paramForValue.ToString().LastIndexOf(","));
  return allParams;
}

Saving Data to Database

This is how I calling my repository instance to save the data from aspx page. In this method, Activator.CreateInstance() is used to create an instance of the specified type.

C#
protected void SaveEnity(object sender, EventArgs e)
{
    //Creating an instance of typeEntity
    var objEntity = Activator.CreateInstance(_typeEntity);
    var allControls = PlaceHolder1.Controls;
    var entityProps = _typeEntity.GetProperties(flag);
    foreach (var control in allControls.Cast<object>().Where
        (control => control.GetType() == typeof (TextBox)))
    {
        var control1 = control;
        foreach (var props in entityProps.Where(props => props.Name == ((TextBox) control1).ID))
        {
            //Setting value in instance property
            props.SetValue(objEntity, ((TextBox) control).Text, null);
            break;
        }
    }
    //Instance of Repository
    var objRepo = Activator.CreateInstance(_typeRepository);
    var allMethods = objRepo.GetType().GetMethods();
    foreach (var method in allMethods.Where(method => method.Name == "Add"))
    {
        //Calling the Method of repository instance
        method.Invoke(objRepo, new[] {objEntity});
        break;
    }
}

Convert Table to List<T>

In this extension method, what I am doing is convert datatable to its representing list of object by matching datatable header with class attributes. This code is actually taken from this article, with some modification.

C#
public static List<T> ToList<T>(this DataTable dataTable) where T : new()
{
    var dataList = new List<T>();
    const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
    var objFieldNames = (from PropertyInfo aProp in typeof(T).GetProperties(flags) 
    where aProp.GetCustomAttributes(typeof(DbColumnAttribute),false).Length>0
        select new
            {
                Name =((DbColumnAttribute)aProp.GetCustomAttributes(typeof(DbColumnAttribute), false)[0]).Name,
                Type = Nullable.GetUnderlyingType(aProp.PropertyType) ?? aProp.PropertyType
             }).ToList();

    var dataTblFieldNames = (from DataColumn aHeader in dataTable.Columns
        select new { Name = aHeader.ColumnName, Type = aHeader.DataType }).ToList();
    var commonFields = objFieldNames.Intersect(dataTblFieldNames).ToList();
    var fro = (from PropertyInfo aProp in typeof(T).GetProperties(flags)
        where aProp.GetCustomAttributes(typeof(DbColumnAttribute), false).Length > 0
        select aProp).ToList();

    foreach (DataRow dataRow in dataTable.AsEnumerable().ToList())
    {
        var aTSource = new T();
        int i = 0;
        foreach (var aField in commonFields)
        {
            //here
            PropertyInfo pi = aTSource.GetType().GetProperty(fro[i++].Name);
            //PropertyInfo propertyInfos = aTSource.GetType().GetProperty(objFieldNames[i++].Name);
            pi.SetValue(aTSource, dataRow[aField.Name] == DBNull.Value ? null : dataRow[aField.Name], null);
        }
        dataList.Add(aTSource);
    }
    return dataList;
}

Performance and Reflection

Reflection come up with the cost. But that cost depends on the implementation (repetitive calls should be cached for example: entity.GetType().GetProperty(BindingFlag.Public)).

For example in this article code example, there are methods GetAddParameter, GetUpdateParameter. In these two methods, I have called obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) each time method is called. For example, if I have a million Widgets and I call your GetAddParameter method for each of them, it would call GetType and GetProperties a million times and each time, it will return exactly the same information as all the other times. This is exceedingly wasteful. So for this scenario Mr. PIEBALDconsult suggested that this method should be cached. So I came up with this final version of GetAddParameter and GetUpdateParameter.

C#
private List<SqlParameter> GetAddParameter(object obj)
{
    var cacheKeyName = ((T)obj).GetTableName();
    var propertyInfos= MyCache.GetMyCachedItem(cacheKeyName);

    if (propertyInfos == null)
    {
        propertyInfos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
        MyCache.AddToMyCache(cacheKeyName, propertyInfos, CacheItemPriority.Default);
    }

    var sqlParams= (from f in propertyInfos let cols = f.GetCustomAttributes(typeof (DbColumnAttribute), false)
        where cols.Length > 0 && f.GetCustomAttributes(typeof (IdentityAttribute), false).Length == 0
        let p = cols[0]
        select new SqlParameter(((DbColumnAttribute) p).Name, f.GetValue(obj, null))).ToList();
    return sqlParams;
}

private List<SqlParameter> GetUpdateParameter(object obj)
{
    var cacheKeyName = ((T)obj).GetTableName();
    var propertyInfos = MyCache.GetMyCachedItem(cacheKeyName);
    if (propertyInfos == null)
    {
        propertyInfos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
        MyCache.AddToMyCache(cacheKeyName, propertyInfos, CacheItemPriority.Default);
    }

    var sqlParams= (from f in propertyInfos let cols = f.GetCustomAttributes(typeof (DbColumnAttribute), false)
        where cols.Length > 0
        let p = cols[0]
        select new SqlParameter(((DbColumnAttribute) p).Name, f.GetValue(obj, null))).ToList();
    return sqlParams;
}

During my learning on how to increase performance while using reflection, I came up with many interesting articles:

Quote:

Reflection is neither bad, nor slow. It is simply a tool. Like all tools, it is very valuable for certain scenarios, not so valuable for others.

Pin Of Interest

What I learned during this example is the main value of Reflection is that it can be used to inspect assemblies, types, and members. It's a very powerful tool for determining the contents of an unknown assembly or object and can be used in a wide variety of cases.

Revision History

  • 18-02-2015: First posted
  • 28-02-2015: Updated example code with cache implementation and refactoring

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
GeneralNice Article Pin
S Gupta26-Feb-15 4:23
S Gupta26-Feb-15 4:23 
QuestionNice article, but lacks warning Pin
David Steverson25-Feb-15 6:45
professionalDavid Steverson25-Feb-15 6:45 
I think the article (while written well enough) could do with a 'warning' that reflection comes with a price, in both performance and memory
GeneralRe: Nice article, but lacks warning Pin
Abhinaw Kumar26-Feb-15 4:17
professionalAbhinaw Kumar26-Feb-15 4:17 
GeneralMy vote of 5 Pin
wsc091825-Feb-15 6:15
wsc091825-Feb-15 6:15 
GeneralRe: My vote of 5 Pin
Abhinaw Kumar26-Feb-15 4:13
professionalAbhinaw Kumar26-Feb-15 4:13 
QuestionI used a lot of reflection when I did this Pin
Sacha Barber25-Feb-15 3:43
Sacha Barber25-Feb-15 3:43 
GeneralRe: I used a lot of reflection when I did this Pin
Abhinaw Kumar26-Feb-15 4:19
professionalAbhinaw Kumar26-Feb-15 4:19 
GeneralRe: I used a lot of reflection when I did this Pin
Sacha Barber27-Feb-15 2:44
Sacha Barber27-Feb-15 2:44 
GeneralMy vote of 5 Pin
Randhir Singh22-Feb-15 5:32
Randhir Singh22-Feb-15 5:32 
GeneralMy vote of 5 Pin
Randhir Singh22-Feb-15 5:26
Randhir Singh22-Feb-15 5:26 
GeneralRe: My vote of 5 Pin
Abhinaw Kumar23-Feb-15 3:51
professionalAbhinaw Kumar23-Feb-15 3:51 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun18-Feb-15 19:23
Humayun Kabir Mamun18-Feb-15 19:23 
GeneralRe: My vote of 5 Pin
Abhinaw Kumar19-Feb-15 3:12
professionalAbhinaw Kumar19-Feb-15 3:12 
GeneralMy vote of 5 Pin
Thomas Maierhofer (Tom)18-Feb-15 17:43
Thomas Maierhofer (Tom)18-Feb-15 17:43 
GeneralRe: My vote of 5 Pin
Abhinaw Kumar19-Feb-15 3:14
professionalAbhinaw Kumar19-Feb-15 3:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.