Dynamic Code/Class Generation (Update)


Overview
Back not to long ago, I released a post dealing with the dynamic generation of classes at run-time. Since then I have applied the concepts shown in that post in my real-world projects. As expected of any new code, I have found several bugs and inconveniences with the original code from that post. This time around I have an updated CodeGenerationHelper class, a DynamicFactory class, and an actual example of how to use the generated objects. The download-able version of the sample can be obtained through this link: http://thrash505.webs.com/DynamicCodeGeneration.zip


CodeGenerationHelper
The CodeGenerationHelper class allows you to easily define and create dynamic class instances. The class is static and contains only two method members: GetCodeString, and InstantiateClass. To create a class first call the GetCodeString method with the appropriate arguments, then store the returned code string for re-use as it can be thought of as your dynamic class’s definition. When you want to create an instance of your dynamic class pass the generated code string into the InstantiateClass method, and a new instance of the class will be returned. However, this code is no longer intended to be used directly because of the existence of the DynamicFactory class which wraps this behavior and has several methods which I’ll describe in the next section.

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;

namespace DynamicCodeGeneration
{
    public static class CodeGenerationHelper
    {
        public static string GetCodeString(string namespaceName, string className, string[] keys, Type[] types)
        {
            // Create compile unit.
            CodeCompileUnit compileUnit = new CodeCompileUnit();

            // Create namespace.
            CodeNamespace dynamicNamespace = new CodeNamespace(namespaceName);
            dynamicNamespace.Imports.Add(new CodeNamespaceImport("System.ComponentModel"));

            // Create class.
            CodeTypeDeclaration dynamicClass = new CodeTypeDeclaration(className);
            dynamicClass.IsClass = true;
            dynamicClass.BaseTypes.Add(new CodeTypeReference("System.ComponentModel.INotifyPropertyChanged"));

            // Create PropertyChanged event; implement INotifyPropertyChanged.
            CodeMemberEvent propertyChangedEvent = new CodeMemberEvent();
            propertyChangedEvent.Name = "PropertyChanged";
            propertyChangedEvent.Type = new CodeTypeReference("System.ComponentModel.PropertyChangedEventHandler");
            propertyChangedEvent.Attributes = MemberAttributes.Public;
            dynamicClass.Members.Add(propertyChangedEvent);

            // Create RaisePropertyChanged method, and add to class.
            CodeMemberMethod raisePropertyChangedMethod = new CodeMemberMethod();
            raisePropertyChangedMethod.Attributes = MemberAttributes.Family | MemberAttributes.Final;
            raisePropertyChangedMethod.Name = "RaisePropertyChanged";
            raisePropertyChangedMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.String", "propertyName"));
            raisePropertyChangedMethod.Statements.Add(new CodeSnippetStatement("if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }"));
            dynamicClass.Members.Add(raisePropertyChangedMethod);

            for (int i = 0; i < keys.Length; i++)
            {
                // Construct field and property names.
                string fieldName = string.Format("_{0}", keys[i].ToString());
                string propertyName = keys[i].ToString();

                // Create field.
                CodeMemberField dynamicField = new CodeMemberField(types[i], fieldName);
                //dynamicField.InitExpression = new CodeDefaultValueExpression(new CodeTypeReference(types[i]));
                dynamicClass.Members.Add(dynamicField);

                // Create property.
                CodeMemberProperty dynamicProperty = new CodeMemberProperty();
                dynamicProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
                dynamicProperty.Name = keys[i].ToString();
                dynamicProperty.Type = new CodeTypeReference(types[i]);

                // Create property - get statements.
                dynamicProperty.GetStatements.Add(new CodeMethodReturnStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));

                // Create property - set statements.
                // Assign value to field.
                dynamicProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(
                    new CodeThisReferenceExpression(), fieldName), new CodePropertySetValueReferenceExpression()));

                // Call RaisePropertyChanged method.
                CodeMethodInvokeExpression raisePropertyChangedMethodInvoke
                    = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "RaisePropertyChanged",
                        new CodeExpression[] { new CodeSnippetExpression("\"" + propertyName + "\"") });
                dynamicProperty.SetStatements.Add(raisePropertyChangedMethodInvoke);

                // Add property to class.
                dynamicClass.Members.Add(dynamicProperty);
            }

            // Add class to namespace.
            dynamicNamespace.Types.Add(dynamicClass);

            // Add namespace to compile unit.
            compileUnit.Namespaces.Add(dynamicNamespace);

            // Generate CSharp code from compile unit.
            StringWriter stringWriter = new StringWriter();
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
            provider.GenerateCodeFromCompileUnit(compileUnit, stringWriter, new CodeGeneratorOptions());
            stringWriter.Close();
            return stringWriter.ToString();
        }

        public static object InstantiateClass(string codeString, string fullyQualifiedTypeName)
        {
            CSharpCodeProvider compiler = new CSharpCodeProvider();
            CompilerParameters compilerParams = new CompilerParameters(new string[] { "System.dll" });
            CompilerResults results = compiler.CompileAssemblyFromSource(compilerParams, new string[] { codeString });
            return results.CompiledAssembly.CreateInstance(fullyQualifiedTypeName);
        }
    }
}

DynamicFactory
The DynamicFactory class wraps the functionality of the CodeGenerationHelper, provides methods for getting/setting properties, and registering/de-registering propertychanged event handlers.

NOTE: There is one minor issue that I have not yet resolved: Anytime a property needs to be get or set, an object instance must be passed in. The DynamicFactory then takes that object instance and get it’s type, then use the type to get its properties as propertyinfos’. The property info is then used to either get or set the property. My intention was to only do this once and save the propertyinfos for re-use, however I end up with a type-mismatch error everytime I call SetValue on them. Therefore, the propertyinfos are retrieved on every call to SetProperty and GetProperty… keep that in mind.

The most important methods on this class are Create, GetProperty, SetProperty, and AddPropertyChangedEventHandler. The constructor takes all the arguments you give it and passes them along to the CodeGenerationHelper to get a code string for the dynamic class and stores them for re-use. After you have an instance of the DynamicFactory you’ll be all set to start creating instances of your dynamic class. This is achieved by calling the Create method, which simply passes the stored code string generated during the constructor to the CodeGenerationHelper. The Create method will return a new instance of your dynamic class. If you intended to be notified of propertychange events you’ll want to make a call to the AddPropertyChangedEventHandler which simply takes a PropertyChangedEventHandler. This handler will be called anytime a property on your dynamic class is changed. The GetProperty and SetProperty methods get and set properties on your dynamic class.

using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace DynamicCodeGeneration
{
    public class DynamicFactory
    {
        private string[] _propertyNames;
        private Type[] _propertyTypes;
        private string _codeString;

        public string NamespaceName { get; private set; }
        public string ClassName { get; private set; }
        public string FullyQualifiedName { get { return string.Format("{0}.{1}", NamespaceName, ClassName); } }

        public DynamicFactory(string namespaceName, string className, string[] propertyNames, Type[] propertyTypes)
        {
            NamespaceName = namespaceName;
            ClassName = className;
            _propertyNames = propertyNames;
            _propertyTypes = propertyTypes;
            _codeString = CodeGenerationHelper.GetCodeString(NamespaceName, ClassName, _propertyNames, _propertyTypes);
        }

        public void AddPropertyChangedEventHandler(object objectInstance, PropertyChangedEventHandler handler)
        {
            EventInfo eventInfo = (from eventHandler in objectInstance.GetType().GetEvents()
                                   where eventHandler.EventHandlerType == typeof(PropertyChangedEventHandler)
                                   select eventHandler).First();
            eventInfo.AddEventHandler(objectInstance, handler);
        }

        public object Create()
        {
            return CodeGenerationHelper.InstantiateClass(_codeString, FullyQualifiedName);
        }

        public object GetProperty(object objectInstance, string propertyName)
        {
            PropertyInfo propertyInfo = FindPropertyInfo(objectInstance, propertyName);

            if (propertyInfo == null)
            {
                throw new ArgumentException(string.Format("The property '{0}' does not exist.", propertyName));
            }

            return propertyInfo.GetValue(objectInstance, null);
        }

        public string[] GetPropertyNames()
        {
            return (string[])_propertyNames.Clone();
        }

        public Type[] GetPropertyTypes()
        {
            return (Type[])_propertyTypes.Clone();
        }

        public PropertyInfo FindPropertyInfo(object objectInstance, string propertyName)
        {
            return (from propertyInfo in objectInstance.GetType().GetProperties()
                    where propertyInfo.Name == propertyName
                    select propertyInfo).First();
        }

        public void RemovePropertyChangedEventHandler(object objectInstance, PropertyChangedEventHandler handler)
        {
            EventInfo eventInfo = (from eventHandler in objectInstance.GetType().GetEvents()
                                   where eventHandler.EventHandlerType == typeof(PropertyChangedEventHandler)
                                   select eventHandler).First();
            eventInfo.RemoveEventHandler(objectInstance, handler);
        }

        public void SetProperty(object objectInstance, string propertyName, object propertyValue)
        {
            PropertyInfo propertyInfo = FindPropertyInfo(objectInstance, propertyName);

            if (propertyInfo == null)
            {
                throw new ArgumentException(string.Format("The property '{0}' does not exist.", propertyName));
            }

            propertyInfo.SetValue(objectInstance, propertyValue, null);
        }
    }
}

Example Usage
This is a very basic example that shows how to use the DynamicFactory class. First an instance of the DynamicFactory class is created, then it is used to create a customer instance, add a propertychanged event handler, get a property, and set a property.

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace DynamicCodeGeneration
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // Create our customer factory.
            DynamicFactory customerFactory = new DynamicFactory("DynamicCodeGeneration", "Customer",
                new string[] { "Id", "FirstName", "LastName", "Address", "PhoneNumber" },
                new Type[] { typeof(int), typeof(string), typeof(string), typeof(string), typeof(string) });

            // Create a customer instance, and register a property changed event handler.
            object customer = customerFactory.Create();
            customerFactory.AddPropertyChangedEventHandler(customer, delegate(object sender, PropertyChangedEventArgs e)
            {
                MessageBox.Show(string.Format("The value for property '{0}' has changed; it is now '{1}'", e.PropertyName,
                    customerFactory.GetProperty(customer, e.PropertyName)));
            });

            // Change the Id property to a value of 1.
            customerFactory.SetProperty(customer, "Id", 1);
        }
    }
}

Limitations
The code presented here is intended for dynamically creating classes at run-time, with a set of properties undetermined at design-time. Support for propertychanged notifications is also included. However there is no support for constructor parameters, adding additional methods, events, operators, etc. I do not intend on implementing this additional functionality because it is beyond the scope of my current needs.

, , , , , , ,

  1. #1 by Tropet Coniconde on April 25, 2013 - 9:56 pm

    With this dynamic class generation, Is it possible to inherit another class?

    • #2 by Tim Valentine on April 26, 2013 - 10:46 am

      Add an additional parameter to GetCodeString method and pass in the class name you want to inherit. Then when constructing the dynamicClass variable (of type CodeTypeDeclaration) you’d call the Add method on its BaseType property and pass in the class name argument. Very similar to where the INotifyPropertyChange is added.

  2. #3 by bappy on October 12, 2012 - 10:12 am

    Ok i know this is a little late but you can really optimize you object creation. Factory.create is really slow because you recompile every time your codestring.

    with this code/replace of the function Create you don t recompile the codestring…

    private static Hashtable CompilerResultHashTable = new Hashtable();
    public static object CreateClass(string codeString, string fullyQualifiedTypeName, CompilerParameters compilerParams)
    {
    if(CompilerResultHashTable[codeString]==null)
    {
    // Create compiler.
    CSharpCodeProvider compiler = new CSharpCodeProvider();

    // Compile code string with compiler params and return a new instance of generated class.
    CompilerResults _compilerResults = compiler.CompileAssemblyFromSource(compilerParams, new string[] { codeString });
    CompilerResultHashTable.Add(codeString,_compilerResults);
    }
    return ((CompilerResults)CompilerResultHashTable[codeString]).CompiledAssembly.CreateInstance(fullyQualifiedTypeName);
    }

  3. #4 by mleyzaola on April 29, 2012 - 2:50 pm

    Oh man! I can’t stress how thankful I am at this moment. This was a feature I was looking for my current project. I needed to make dynamic classes for each of my users, with these code, it works like a charm.
    Gr8! Thanks a bunch.

  4. #5 by NK on September 17, 2011 - 2:58 am

    Excellent work, you have saved my time

  5. #6 by Pranav Dixit on July 23, 2011 - 7:45 am

    You code is breaking when I try to create dynamic class with any object type property, like for “customer” if I try to create Order type property for customer object then “customerFactory.Create()” always return null. Do know what It wrong with it.

    If you can help the get ride of it?

    Thanks in advance.

  6. #9 by Rajan Kumar on May 17, 2011 - 10:10 am

    Yes, it worked. Thank You.
    But make the following line “false”, otherwise old assembly stays in memory.
    compilerParams.GenerateExecutable = false;// true;

    • #10 by Tim Valentine on May 17, 2011 - 10:30 am

      Glad I could help. I’ll update this post with the changes soon.

  7. #11 by Rajan Kumar on May 12, 2011 - 1:28 pm

    This is a nice article.
    Could you please help me with the following issue?
    I have created two objects and return List. For example

    List objcoll = new List();
    object customer = customerFactory.Create();
    customerFactory.SetProperty(customer, “FNAME”, “rajan”);
    objcoll.Add(customer);
    customer = customerFactory.Create();
    customerFactory.SetProperty(customer, “FNAME”, “kumar”);
    objcoll.Add(customer);

    When I am using WinForm application and try to bind with DataGrid Control , I get exception in second row “”object does not match target type”
    If I use only one object , then it works.

    dataGridView1.DataSource = objcoll;

    Please help me .

    • #12 by Tim Valentine on May 16, 2011 - 3:54 pm

      Try this:

      public static object InstantiateClass(string codeString, string fullyQualifiedTypeName)
      {
      // Setup compiler.
      CSharpCodeProvider compiler = new CSharpCodeProvider();
      CompilerParameters compilerParams = new CompilerParameters(new string[] { “System.dll” });

      // Ensure output is the executing assembly.
      compilerParams.GenerateExecutable = true;
      compilerParams.GenerateInMemory = false;
      compilerParams.OutputAssembly = Assembly.GetExecutingAssembly().FullName;

      // Compile code string with compiler params and return a new instance of generated class.
      CompilerResults compilerResults = compiler.CompileAssemblyFromSource(compilerParams, new string[] { codeString });
      return compilerResults.CompiledAssembly.CreateInstance(fullyQualifiedTypeName);
      }

      That seems to be working correctly. I thought I had it working before, but the error only occurred when multiple objects were added to the datagrid, not just one. The error appears to be related to what assembly creates the objects and ultimately in what assembly the class resides. Then I set the executing assembly to create the objects, but forgot to set the flags to compile as an executable, so it ignored me. With those flags set, it appears to be working even with multiple objects added to the datagrid’s DataSource.

  8. #13 by Steve Ives on January 4, 2011 - 2:48 pm

    Thanks for this – I found your code to be very useful. By the way, if you want to get rid of the “virtual” keyword on the properties and method, then add “MemberAttributes.Final” when you create the properties and method.

    • #14 by Tim Valentine on February 3, 2011 - 1:35 pm

      Thanks; post updated.

  1. C# CodeGenerationHelper – Dynamic Classes – Create Class From Hashtable at Run-time « Thrash505’s Code Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: