In my last post, I generated a SQL result set by combining a standard query with associated embedded XML metadata:

Regardless of how such a result set was obtained, my goal was to use it to create a dynamic class on the fly and display that class in a .Net PropertyGrid control.
I started with a default VS 2005 WinForms app. I renamed it and added a PropertyGrid control, whose Dock property was set to Fill. No magic here.

The PropertyGrid is designed to display the public properties of a class while fully respecting the specifics of property type. Writeable properties are editable. Read-only properties are not. Property attributes for Category and Description are respected, if present.
The code below creates a dynamic class having private fields and corresponding public properties which map to the query result set. Setters and getters are created for each record, but the setter may be omitted for read-only properties. No constructor is explicitly defined, but one can be created if there is a need for an overload of the default constructor. Since each class element is created directly from MSIL instructions, I found it useful to keep things as simple as possible. After all, I just needed a shim class to couple my query results to the PropertyGrid.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace Dynamic_Classes
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
this.propertyGrid.SelectedObject = this.BuildDynamicClass();
}
protected object BuildDynamicClass()
{
// Define the dynamic assembly, module and type
AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
AssemblyBuilder assemblyBuilder =
Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");
TypeBuilder typeBuilder= moduleBuilder.DefineType("DynamicType", TypeAttributes.Public);
string dataconn = "server=(local);database=dynamic_classes;uid=sa;pwd=password;";
using (SqlConnection cn = new SqlConnection(dataconn))
{
cn.Open();
string sql = "GetWidgetInfo 1";
SqlDataAdapter da = new SqlDataAdapter(sql, cn);
DataTable dt = new DataTable();
da.Fill(dt);
// Create dynamic properties corresponding to query results
foreach (DataRow row in dt.Rows)
{
string name = (string)row["name"];
string category = (string)row["category"];
string description = (string)row["description"];
Type dataType = Type.GetType((string)row["data_type"]);
this.BuildProperty(typeBuilder, name, category, description, dataType);
}
// Create and instantiate the dynamic type
Type type = typeBuilder.CreateType();
Object dynamicType = Activator.CreateInstance(type, new object[] { });
// Set each property's default value
foreach (DataRow row in dt.Rows)
{
string name = (string)row["name"];
Type dataType = Type.GetType((string)row["data_type"]);
object value = row["value"];
value = (Convert.IsDBNull(value)) ? null : Convert.ChangeType(value, dataType);
type.InvokeMember( name,
BindingFlags.SetProperty,
null,
dynamicType,
new object[] { value });
}
return dynamicType;
}
}
protected void BuildProperty( TypeBuilder typeBuilder,
string name,
string category,
string description,
Type fieldType)
{
// Generate the private field/public property name pair
// (field begins w/LC, property begins w/UC)
char[] chars = name.ToCharArray();
chars[0] = char.ToLower(chars[0]);
string fieldName = new string(chars);
chars[0] = char.ToUpper(chars[0]);
string propertyName = new string(chars);
// Create the private field
FieldBuilder fieldBuilder = typeBuilder.DefineField( name,
fieldType,
FieldAttributes.Private);
// Create the corresponding public property
PropertyBuilder propertyBuilder =
typeBuilder.DefineProperty( propertyName,
System.Reflection.PropertyAttributes.HasDefault,
fieldType,
null);
// Define the required set of property attributes
MethodAttributes propertyAttributes = MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig;
// Build the getter
MethodBuilder getter = typeBuilder.DefineMethod( "get_" + propertyName,
propertyAttributes,
fieldType,
Type.EmptyTypes);
ILGenerator getterIlGen = getter.GetILGenerator();
getterIlGen.Emit(OpCodes.Ldarg_0);
getterIlGen.Emit(OpCodes.Ldfld, fieldBuilder);
getterIlGen.Emit(OpCodes.Ret);
// Build the setter
MethodBuilder setter = typeBuilder.DefineMethod( "set_" + propertyName,
propertyAttributes,
null,
new Type[] { fieldType });
ILGenerator setterIlGen = setter.GetILGenerator();
setterIlGen.Emit(OpCodes.Ldarg_0);
setterIlGen.Emit(OpCodes.Ldarg_1);
setterIlGen.Emit(OpCodes.Stfld, fieldBuilder);
setterIlGen.Emit(OpCodes.Ret);
// Bind the getter and setter
propertyBuilder.SetGetMethod(getter);
propertyBuilder.SetSetMethod(setter);
// Set the Category and Description attributes
propertyBuilder.SetCustomAttribute(
new CustomAttributeBuilder(
typeof(CategoryAttribute).GetConstructor(
new Type[] { typeof(string) }), new object[] { category }));
propertyBuilder.SetCustomAttribute(
new CustomAttributeBuilder(
typeof(DescriptionAttribute).GetConstructor(
new Type[] { typeof(string) }), new object[] { description }));
}
}
}
This illustrates the true power of the PropertyGrid. Simply binding a class with public properties provides an amazing amount of functionality - from a single line of code. The grid provides editors and validation for strings, integer and real numbers and dates. Booleans are presented as True/False dropdowns. Even things like the Font Dialog, Color Picker, etc. work with no modification.

I'll follow up soon with a post on how to add custom editors for nonstandard property types. I'll also cover how changes can be saved to the underlying data source when using dynamic classes.