Saturday 25 February 2012

Programmatically Compile And Use Code Using C#

 

Introduction

It is always better if you could create all your required classes in your project. But, sometimes it becomes very crucial if you could create a class for the specific situation at runtime or if you could change the existing one.

To overcome this situation, .Net Framework exposes some class that allow us to programmatically access the C# language compiler.

In this post I am demonstrating how to create an entity class and its collection class through a windows application, and passing the collection class object to the DataGridView as DataSource.

Note: I am assuming you have the basic knowledge of reflection.

How To

Setup the project and form

In this example I am using the basic windows forms application. You can follow the following steps to setup this application -

  • Create a Windows Forms Application. It will create a form with name Form1.
  • Add a button on Form1 and name it btnCompile.
  • Add a DataGridView. Here I am using its default name dataGridView1.
  • Double click on the btnCompile to generate the event handler.

 

Implement the compiler

After adding code in event handler, your code should look like -

private void btnCompile_Click(object sender, EventArgs e)
{
    // code string to compile
    string code =
@"class EntityCollection : System.Collections.Generic.List<Entity> {}

class Entity
{
    public string Name { get; set; }

    public int Age { get; set; }
}";

    // RETRIEVING COMPILER INTERFACE
    CSharpCodeProvider codeProvider = new CSharpCodeProvider();
    ICodeCompiler codeCompiler = codeProvider.CreateCompiler();


    // PREPARING COMPILER PARAMETERS
    CompilerParameters compilerParameters = new CompilerParameters();
    compilerParameters.GenerateInMemory = true;
    compilerParameters.TreatWarningsAsErrors = false;
    compilerParameters.WarningLevel = 4;
    compilerParameters.ReferencedAssemblies.Add("System.dll");

    // COMPILING CODE
    CompilerResults results = codeCompiler.CompileAssemblyFromSource(compilerParameters, code);

    if (results.Errors.Count > 0)
    {
        StringBuilder sbExceptions = new StringBuilder();

        foreach (CompilerError CompErr in results.Errors)
        {
            sbExceptions.AppendLine(
                            "Line number " + CompErr.Line +
                            ", Error Number: " + CompErr.ErrorNumber +
                            ", '" + CompErr.ErrorText + ";" +
                            Environment.NewLine + Environment.NewLine);
        }

        MessageBox.Show("Exception raised while compiling your code: \n\n" + sbExceptions.ToString());
    }
    else
    {
        // GETTING COMPILED ASSEMBLY
        Assembly assembly = results.CompiledAssembly;

        // RETRIEVING COLLECTION CLASS FROM COMPILED ASSEMBLY.
        Type type = assembly.GetType("EntityCollection");

        // CREATING INSTANCE OF COLLECTION CLASS
        IList list = (IList)Activator.CreateInstance(type);

        // RETRIEVING ENTITY CLASS FROM COMPILED ASSEMBLY.
        Type eType = assembly.GetType("Entity");
        object obj = Activator.CreateInstance(eType);

        // ASSIGNING VALUES TO THE ENTITY CLASS OBJECT.
        eType.GetProperty("Name").SetValue(obj, "Sanjay", null);
        eType.GetProperty("Age").SetValue(obj, 30, null);

        // ADDING CLASS TO COLLECTION OBJECT
        list.Add(obj);

        // ASSIGNED LIST TO GRID VIEW AS DATA SOURCE
        dataGridView1.DataSource = list;
    }
}

About code

.NET Framework provides the ICodeCompiler compiler execution interface.The CSharpCodeProvider class implements this interface and provides access to the C# code compiler. The following code creates an instance of CSharpCodeProvider and initializes ICodeCompiler interface object.

CSharpCodeProvider codeProvider = new CSharpCodeProvider();
ICodeCompiler codeCompiler = codeProvider.CreateCompiler();

Now you have the compiler interface which can be used to compile your source code. You need to pass parameters to the compiler by using the CompilerParameters class.

CompilerParameters compilerParameters = new CompilerParameters();
compilerParameters.GenerateInMemory = true;
compilerParameters.TreatWarningsAsErrors = false;
compilerParameters.WarningLevel = 4;
compilerParameters.ReferencedAssemblies.Add("System.dll");

CompilerResults results = codeCompiler.CompileAssemblyFromSource(compilerParameters, code);

CompilerParameters object is used to pass parameters to tell the compiler that you want to generate a class library in memory which can be used further in the code. To compile code you will use function CompileAssemblyFromSource. CompileAssemblyFromSource method takes the compilerParameters object and the source code, which is a string.

Once the code is compiled, you can check to see if there were any compilation errors. You can use the return value from CompileAssemblyFromSource method, which is a CompilerResults object. This object contains an errors collection, which contains any errors that occurred during the compilation process.

Below code collects all the errors in the string builder class which is for demonstration only which you can user for your purpose -

if (results.Errors.Count > 0)
{
    StringBuilder sbExceptions = new StringBuilder();

    foreach (CompilerError CompErr in results.Errors)
    {
        sbExceptions.AppendLine(
                        "Line number " + CompErr.Line +
                        ", Error Number: " + CompErr.ErrorNumber +
                        ", '" + CompErr.ErrorText + ";" +
                        Environment.NewLine + Environment.NewLine);
    }

    MessageBox.Show("Exception raised while compiling your code: \n\n" + sbExceptions.ToString());
}

Once the assembly is generated without any error you can use it in your code. I have used it to generate a list of items and binded this list to the grid view as its data source -

else
    {
        // GETTING COMPILED ASSEMBLY
        Assembly assembly = results.CompiledAssembly;

        // RETRIEVING COLLECTION CLASS FROM COMPILED ASSEMBLY.
        Type type = assembly.GetType("EntityCollection");

        // CREATING INSTANCE OF COLLECTION CLASS
        IList list = (IList)Activator.CreateInstance(type);

        // RETRIEVING ENTITY CLASS FROM COMPILED ASSEMBLY.
        Type eType = assembly.GetType("Entity");
        object obj = Activator.CreateInstance(eType);

        // ASSIGNING VALUES TO THE ENTITY CLASS OBJECT.
        eType.GetProperty("Name").SetValue(obj, "Sanjay", null);
        eType.GetProperty("Age").SetValue(obj, 30, null);

        // ADDING CLASS TO COLLECTION OBJECT
        list.Add(obj);

        // ASSIGNED LIST TO GRID VIEW AS DATA SOURCE
        dataGridView1.DataSource = list;
    }


Once the compilation successes, build assembly can be retrieved from CompileAssemblyFromSource method’s return value which contains CompiledAssembly property.

Now you have the compiled assembly. You can play with it as you wish.

No comments:

Post a Comment