Using Reflection to Determine whether an Type is Nullable And Get the underlying Type

I've mentioned in a previous post that we use Aspose.Words combined with Aspose.Pdf to create PDF documents/reports in all our applications.

To do this we use a method within the Aspose.Words.Reporting.MailMerge class called ExecuteWithRegions using the overload that passes in a DataTable. Within Aspose.Words this effectively loops through the fields in DataTable and creates what you'd normally refer to as a sub-report.

Within our applications we use classes to represent our business objects. As we produce quite a few reports that rely on a number of different objects, we've created a helper method that turns our business objects and/or a List of business objects into DataTables.

We came into a problem recently when one of our fields was defined as "int?" (same as Nullable<int>). The problem we had was trying to add a column on type "int?" to a DataTable. Make sense when you think about it... databases have had the concept of nullable fields for as long as I've been creating databases.

Anyway... rather than add a field of type "int?" to the DataTable, I needed to add a field of type "int" as a column. Sounded simple... sort of... A fair bit of Googling and pulling together ideas from a couple of different blog posts led me to the following code.

The code below takes a List of objects (simple... this doesn't handle complex types) and then returns a DataTable which is a representation of the object.

/// <summary>
 /// Converts a Generic List into a DataTable
 /// </summary>
 /// <param name="list"></param>
 /// <param name="typ"></param>
 /// <returns></returns>
 private DataTable GetDataTable(IList list, Type typ)
 {
     DataTable dt = new DataTable();

     // Get a list of all the properties on the object
     PropertyInfo[] pi = typ.GetProperties();

     // Loop through each property, and add it as a column to the datatable
     foreach (PropertyInfo p in pi)
     {
         // The the type of the property
         Type columnType = p.PropertyType;

         // We need to check whether the property is NULLABLE
         if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
         {
             // If it is NULLABLE, then get the underlying type. eg if "Nullable<int>" then this will return just "int"
             columnType = p.PropertyType.GetGenericArguments()[0];
         }

         // Add the column definition to the datatable.
         dt.Columns.Add(new DataColumn(p.Name, columnType));
     }

     // For each object in the list, loop through and add the data to the datatable.
     foreach (object obj in list)
     {
         object[] row = new object[pi.Length];
         int i = 0;

         foreach (PropertyInfo p in pi)
         {
             row[i++] = p.GetValue(obj, null);
         }

         dt.Rows.Add(row);
     }

     return dt;
 }

The key points from the code above are:

  • using PropertyType.IsGenericType to determine whether the property is a generic type
  • using ProprtyType.GetGenericTypeDefinition() == typeof(Nullable<>) to test whether its a nullable type
  • getting the underlying type using PropertyType.GetGenericArguments() to get the base type.

To use the code above, you can do the following. The example below is a fairly contrived example, but it should highlight what I'm trying to do below.

public class Person
{
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public DateTime? DateOfDeath { get; set; }
}

public class Example
{
    public static DataTable RunExample()
    {
        Person edward = new Person() { Name = "Edward", DateOfBirth = new DateTime(1900, 1, 1), DateOfDeath = new DateTime(1990, 10, 15) };
        Person margaret = new Person() { Name = "Margaret", DateOfBirth = new DateTime(1950, 2, 9), DateOfDeath = null };
        Person grant = new Person() { Name = "Grant", DateOfBirth = new DateTime(1975, 6, 13), DateOfDeath = null };

        List<Person> people = new List<Person>();

        people.Add(edward);
        people.Add(margaret);
        people.Add(grant);

        DataTable dt = GetDataTable(people, typeof(Person));

        return dt;
    }
}

And this will return a DataTable that looks like the following (I'm an Aussie, so the date format is dd/MM/yyyy):

Name (string) DateOfBirth (DateTime) DateOfDeath (DateTime)
Edward 1/1/1900 15/10/1990
Margaret 9/2/1950 [NULL]
Grant 13/6/1975 [NULL]

NOTE: as a general rule we try to NOT use nullable fields in the database, as nullable fields do some REALLY weird things to queries. Pretty much the only field types we use as nulls are DateTime fields. In my opinion, nullable fields get used far too often within databases and there is usually an alternative.

5 Comments

  • Alternatively:

    Type columnType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;

  • You don't need to specify the type of the list.
    Just use:
    list.GetType().GetGenericArguments()[0];

  • I'm finding (i think) that i can simply use p.PropertyType.BaseType if the type I'm inspecting is an anonymous type (i.e. from a LINQ IEnumerable). I'm trying to figure out the drawback of this vs the techniques described above.

  • This is great! Thanks for posting.

  • Well done, good post.
    I generalized nullable check in my code with
    MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, property);
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if (property.PropertyType.IsGenericType &amp;&amp; property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable&lt;&gt;))
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PropertyInfo valueProperty = property.PropertyType.GetProperty("Value");
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;propertyAccess = Expression.MakeMemberAccess(propertyAccess, valueProperty);
    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}

Comments have been disabled for this content.