Introduction
I occasionally implement a program where I wish I could either change some parameters interactively or re-write a portion of code interactively. The parameters are easily solved with existing GUI components. The .NET framework, with the System.CodeDom
namespace, provides a nice suite of tools for the latter. Several tutorials on using the CodeDom document exist, with most of them focusing on creating a source code file. In .NET 2.0, support was added to compile code from an internal string. This article will go over how to take a section of code from, say, a text box, and immediately use it within your system. I will also go over a few simple use cases that allow you to take a single expression or calculation from the user and embed that into a delegate function or your own interface hidden from the user. In part II, I will provide some robustness issues, provide additional examples, and discuss the utility of this with respect to the new LINQ technology. Note, I will use C# to explain this, but the concept will work equally well with any .NET language.
Initial Set-up
For this part of the tutorial, we will focus on a simple task: Outputting a string to the console. Let us start with this simple program, and see how we can refactor it to allow for more flexibility. The article is long, but mainly because I am going to take baby steps and repeat sections of the code as they are refactored. I am also going to ignore any error checking or exceptions until the end of the tutorial. Consider this very simple console application:
class Foo
{
public void Print()
{
System.Console.WriteLine("Hello from class Foo");
}
}
static void Main( string[] args )
{
Foo myFoo = new Foo();
myFoo.Print();
}
Dynamic Compilation
My end goal is to take the source code in as a text string. So, as a first test, I convert the code above to a string, and then in Main
, I will create an instance of Foo
using the CodeDom technology and Reflection. This requires three steps:
- Create an instance of an object that supports the
ICodeCompiler
interface. For our purposes, we want an instance which deals with C#, so we will use the CSharpCodeProvider
in the Microsoft.CSharp
namespace. - Compile the source code into an Assembly.
- Use Reflection on the Assembly to get information on the types, and create an instance of a type.
To aid in this, I will create a utility method called CompileSource
that will take in a string and output an Assembly. Here is the code for this:
private static Assembly CompileSource( string sourceCode )
{
CodeDomProvider cpd = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("ClassLibrary1.dll");
cp.GenerateExecutable = false;
CompilerResults cr = cpd.CompileAssemblyFromSource(cp, sourceCode);
return cr.CompiledAssembly;
}
Fairly straightforward; in addition to creating the ICodeCompiler
and compiling the source, we needed to create a CompilerParameters
instance and configure it. Note that you can add any reference that you wish to expose to your users. The resulting Assembly is extracted from an instance of the CompilerResults
class that the CompileAssemblyFromSource
method returns. Okay, now let’s put this to use. Below is the new application (minus the CompileSource
code above). I am using the string @
operator to specify the source code. With this syntax, any quotation marks in the string need to be repeated:
namespace CreateDelegate
{
class Program
{
static void Main( string[] args )
{
Test1();
}
private static void Test1()
{
string FooSource = @"
class Foo
{
public void Print()
{
System.Console.WriteLine(""Hello from class Foo"");
}
}";
Assembly assembly = CompileSource(FooSource);
object myFoo = assembly.CreateInstance("Foo");
}
}
}
Note, Main
has successfully created an instance of the Foo
class, but there are a few problems:
Main
has knowledge of the class name (“Foo”) hard-coded within it.- When you compile this application, the type
Foo
does not exist. It is created at run-time. As such, we have to use the unified type object
to deal with our instance of Foo
. There is no way to cast it to the type Foo
. - As a result of 2 above, I can not call
Print
using myFoo.Print
. That will produce a compiler error, as the type object
does not have a method Print
.
Hence, the new version does not print anything. Using Reflection, it is possible to get the list of methods for the class Foo
and to Invoke
the Print
method directly. Doing so directly is rather clumsy, and not a very useful programming practice. Instead, I will cover two approaches to deal with these types, that provide a more elegant solution:
- Programming to Interfaces
- Using Delegates
Both of these require additional knowledge in either the main program, the source code string, or both. Which one to use depends on your application’s particular needs, and will be covered in the use cases later.
Programming to Interfaces
A fundamental tenant of modern software design is to program to interfaces. For our simple example, we will add an IPrint
interface:
namespace FooLibrary
{
public interface IPrint
{
void Print();
}
}
I have placed this in the namespace FooLibrary
. Having an interface is not sufficient. It needs to be accessible to any class that wishes to implement it. The best way to accomplish this is to place your key interfaces into one or more type libraries (DLLs). I have created the DLL IPrint.dll for this project. I have also added it as a reference to our console application. The modified test is as follows:
using FooLibrary;
private static void Test2()
{
string FooInterface = @"
class Foo : FooLibrary.IPrint {
public void Print() {
System.Console.WriteLine(""Hello from class Foo"");
}
}";
Assembly assembly = CompileSource(FooInterface);
IPrint foo = assembly.CreateInstance("Foo") as IPrint
if (foo != null)
{
foo.Print();
}
}
We have made the following changes:
- We added a
using
statement for FooLibrary
. - We cast the result of
CreateInstance
to the type IPrint
(using the as
operator). - If the cast is successful, we can now call
Print
().
In order for our string-based source code to compile, it needs access to the type IPrint
in the IPrint.dll assembly. Our CompileSource
method needs the following line added:
cp.ReferencedAssemblies.Add("IPrint.dll");
Note that even though we include a using
statement for FooLibrary
, we still need to fully qualify IPrint
within the string fooSource
. This makes sense, since it is treated as a separate compile unit. We could have also added an additional using FooLibrary
inside the string.
Searching for Types Using Reflection
This solves two of the three problems mentioned above. Main
still has a hard-coded reference to the string name “Foo”. Having the name of the class hard-wired can easily be replaced with an additional user control, letting the user specify the name of the type to create. This would be error prone as the user changes the code, but perhaps forgets to change the name. To remove the implementation intelligence of knowing the concrete type name from Main
, we can search the assembly. What we want is to take an assembly and a well-known interface name, and search for the concrete type name that implements the interface. This is accomplished as follows:
private static void Test2()
{
string FooInterface = @"
class Foo : FooLibrary.IPrint {
public void Print() {
System.Console.WriteLine(""Hello from class Foo"");
}
}";
Assembly assembly = CompileSource(FooInterface);
Type fType = assembly.GetTypes()[0];
Type iType = fType.GetInterface("FooLibrary.IPrint");
if (iType != null)
{
FooLibrary.IPrint foo = (FooLibrary.IPrint)
assembly.CreateInstance(fType.FullName);
foo.Print();
}
}
Here, we made the assumption that there is only one type, or the type we are interested in is the first one. This can easily be changed to search through all types, and is left as an exercise to the reader. So, we can now create instances of a type supporting our interface, where the type definition starts out initially as a text string. If the end goal is simply to execute the statements within the Print()
method, then a simpler scheme can be constructed using delegates, which we will talk about next.
Dynamic Delegates
Let’s consider the case where we wish to allow the user to specify an arbitrary two-dimensional function, f(x,y), using some user-interface component. Here is an example:
float func2D( float x, float y)
{
float value; value = Math.Cos(x) * Math.Sin(x) + 1.0f;
return value;
}
There are many articles about creating your own expression engine and parsing a function like this one. With CodeDom, you can treat any .NET language as your scripting language. How is this function different from our Print
method above? Well, for one, it is not contained in a class or a namespace. You can use Reflection to access this function from the global namespace, but I am going to take a different route. I am going to wrap it in a class so we can use a similar logic to the above. I structured this code very specifically. The input I want from the user is simply the line (or set of statements):
value = Math.Cos(x) * Math.Sin(y) + 1.0f;
This will alleviate the user from knowing the exact function signature, the class name, interface name, etc. How? Through string manipulations! I define two additional strings, called methodHeader
and methodFooter
. I then take the string for the expression, and sandwich it between the header and footer strings before passing it to the CompileSource
method. For example, the original example can be coded like so:
string FooSourcHeader = @"
using System;
class Foo {
static public void Print()
{";
string FooSourceFooter = @"
}
}";
string myPrint = FooSourcHeader
+ @"Console.WriteLine(""Embedded Hello"");"
+ FooSourceFooter;
Assembly fooAssembly = CompileSource(myPrint);
IPrint myFoo = fooAssembly.CreateInstance("Foo") as IPrint;
myFoo.Print();
What is the advantage of doing this? Well, now I know that “Foo” is the class name and that it implements the IPrint
interface. I have control over those strings! The user only needs to know that they are to provide a sequence of zero or more statements. I can even provide a more detailed class definition and tell the users which fields they have access to. Okay, so that is cool, but this section is about dynamic delegates. A delegate defines a type for a method signature. They are commonly used in callbacks and with events. Any method with the same signature can be treated as the delegate’s type.
delegate void PrintDelegate();
private static void Test3()
{
string FooSourcHeader = @"
using System;
class Foo {
static public void Print()
{";
string FooSourceFooter = @"
}
}";
string myPrint = FooSourcHeader
+ @"Console.WriteLine(""Hello from Print delegate"");"
+ FooSourceFooter;
Assembly assembly = CompileSource(myPrint);
Type fooType = assembly.GetType("Foo");
MethodInfo printMethod = fooType.GetMethod("Print");
PrintDelegate fooPrint = (PrintDelegate)
Delegate.CreateDelegate(typeof(PrintDelegate), printMethod);
fooPrint();
}
Summary
There you have it. We can use text strings to add functionality to our system at run-time. We can allow full class definitions if we want, or simple method bodies. In part II, I will provide some user controls and example demos built with these ideas. I will also go over some robustness issues, and how to present any compiler errors back to the user.
History
- 05-22-2008 - Initial article.