Tuesday, August 7, 2007

Reflect the underlying type of nullable property / field

Sometimes it's necessary list the properties with their types of an entity instanciated at run-time. For example when develop a CRUD form at runtime for a selected entity or when build a designer form like a report designer or expression designer that implies entities and her properties. This task is not problematic using Reflection, but if we use Nullable properties (and we would have to use it whenever a value can be null) the underlying type of a nullable property can't be obtained in the traditional way. A nullable property is a "constructed type" of the Nullable<T> generic class, this means that the type of the property is the parameter for the constructor of Nullable<T> (For example a nullable int property is an instance of Nullable<int>). I wrote a method that list the properties with their types iterating over the reflected properties adding his name and type to a dictionary, and when it finds a generic property obtains the underlying type of the property using the argument that was supplied to construct the nullable type.
Below a sample of using the method to list the properties and types of a run-time instantiated entity.


using System;
using System.Web;
using System.Collections.Generic;
using System.Reflection;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

public static SortedDictionary<string, string> GetPropertiesAndDataTypes(string entityName)
{
SortedDictionary<string, string> dict = new SortedDictionary<string, string>();
string propertyType = string.Empty;
if (!String.IsNullOrEmpty(entityName))
{
Type type = Type.GetType(entityName);
if (type != null)
{
PropertyInfo[] properties = type.GetProperties();
if (properties != null && properties.Length > 0)
{
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.PropertyType.IsGenericType)
{
Type nullableProperty = Type.GetType(propertyInfo.PropertyType.FullName);
// Obtains the Name of the type used as parameter of Nullable<T>
propertyType = nullableProperty.GetGenericArguments()[0].FullName;
}
else
{
propertyType = propertyInfo.PropertyType.FullName;
}
dict.Add(propertyInfo.Name, string.Format("{0} ({1})", propertyInfo.Name, propertyType));
}
}
}
}
return dict;
}

protected void ddlEntities_SelectedIndexChanged(object sender, EventArgs e)
{
lbEntityProperties.Items.Clear();
if (ddlEntities.SelectedIndex > 0)
{
lbEntityProperties.DataSource = GetPropertiesAndDataTypes(ddlEntities.SelectedValue);
lbEntityProperties.DataTextField = "Value";
lbEntityProperties.DataValueField = "Key";
lbEntityProperties.DataBind();
}
}
}

public class Product
{
private int _productId;
private string _name;
private bool? _makeFlag;
private DateTime _selStartDate;
private DateTime? _selEndDate;
private int? _daysToManufacture;

public int ProductId
{
get { return _productId; }
set { _productId = value; }
}

public string Name
{
get { return _name; }
set { _name = value; }
}

public bool? MakeFlag
{
get { return _makeFlag; }
set { _makeFlag = value; }
}

public DateTime SelStartDate
{
get { return _selStartDate; }
set { _selStartDate = value; }
}

public DateTime? SelEndDate
{
get { return _selEndDate; }
set { _selEndDate = value; }
}

public int? DaysToManufacture
{
get { return _daysToManufacture; }
set { _daysToManufacture = value; }
}

public Product()
{

}

}

public class Contact
{
private string _name;
private int? _score;
private bool? _isMember;

public Contact()
{ }

public int? Score
{
get { return _score; }
set { _score = value; }
}

public string Name
{
get { return _name; }
set { _name = value; }
}

public bool? IsMember
{
get { return _isMember; }
set { _isMember = value; }
}

}

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Reflect type of nullable properties</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:DropDownList ID="ddlEntities" runat="server" AutoPostBack="True" OnSelectedIndexChanged="ddlEntities_SelectedIndexChanged">
<asp:ListItem>-- Select Entity --</asp:ListItem>
<asp:ListItem>Contact</asp:ListItem>
<asp:ListItem>Product</asp:ListItem>
</asp:DropDownList><br /><br />
<asp:ListBox ID="lbEntityProperties" runat="server" Height="258px" Width="265px"></asp:ListBox>
</div>
</form>
</body>
</html>

3 comments:

Lucas Ontivero said...

Mas que para Nullable es para todo tipo de generics y veo que de los parametros del tipo generico obtenes solo el primero. Capaz que convenga preguntar especificamente si es Nullable.
Un abrazo Tincho y seguí dando cátedra!

Martin Olivares said...

Seguro que es mejor lo que sugerís, lo voy a probar y hago un update. Obtengo solo el primer parámetro genérico presuponiendo que las propiedades nullables son de tipos primitivos (int, bool, Datetime, etc.) pero es cierto que en ciertos casos esto puede que no sea así.

Saludos!

Anonymous said...

Thankyou!