Posted by: rcosic | 20/03/2009

Duck typing

duck

Yes, you would say I’m crazy, but it’s a real principle in programming called duck-typing. It is assumable that if something walks like a duck and quacks like a duck we could call it a duck. On the first look, it looks like nonsense, but if you change the word duck with a class definition (or interface) and walking and quacking with some member definitions (methods, properties, events) you could possibly create an object (i.e. an instance of a class) by examining its interface.

This style should not be confused with anonymous types, in which case a programmer defines class members on-the-fly, but though producing static entities. Attempting to access a nonexistent field will cause a compiler error at compile time, while in duck-typing this is left unreveiled until the code is actually ran:

var person = new {FirstName = "John", LastName = "Smith"}

Although it seems unreliable, duck-typing is by definition type-safe since one can only invoke operations an object actually implements. Duck typing differs from structural typing in that, if the part (of the whole module structure) needed for a given local computation is present at runtime, the duck type system is satisfied in its type identity analysis. On the other hand, a structural type system would require analysis of whole module structure at compile-time to determine type identity or type dependence.

Nah, whatever, let’s get back to our example from the previous post…

With the code type declaration being filled in code compile unit, we can begin with the compilation. But first, I will rearrange the Main function a little bit to pass the result as a parameter to the compilation method, and so on:

static void Main(string[] args)
{
Type targetType = typeof(IClientClass);

_assemblyPaths.Clear();

CodeCompileUnit codeCompileUnit = GenerateClass(targetType);
if (codeCompileUnit != null)
{
Assembly compiledAssembly = CompileCode(targetType, codeCompileUnit, true);
if (compiledAssembly != null)
{
object instance = compiledAssembly.CreateInstance(
string.Format(“{0}.{1}”, targetType.Namespace, targetType.Name.Substring(1)));
}
}
}

Make note that we are going to do the compilation routine, which consists of the following steps:

  1. as a prerequisite, a fully stuffed CodeCompileUnit is assumed to be available,
  2. picking the right code provider (thy it be CSharpCodeProvider or other),
  3. writing the class file (if specified),
  4. building the parameters for source compilation,
  5. registering referenced parameters of the target type,
  6. adding assembly references of all related assemblies (found in a dictionary),
  7. registering referenced assemblies of the caller’s type (!),
  8. invoking the compilation,
  9. displaying compilation errors (if required),
  10. returning compiled assembly,
  11. creating an instance of the class.

As the code is generally straightforward, I’ll just paste it and after that explain the major points:

private static Assembly CompileCode(Type targetType, CodeCompileUnit codeCompileUnit, bool generateClassFile)
{
// generating code (I’ve choose C# for this example)
CSharpCodeProvider csharpProvider = new CSharpCodeProvider();

// writing class file (if specified)
if (generateClassFile)
{
DirectoryInfo directoryInfo = new DirectoryInfo(@”E:temp”); // a little-bit hard-coded, but you’ll change it
if (directoryInfo.Exists == false)
directoryInfo.Create();

TextWriter writer = new StreamWriter(Path.Combine(directoryInfo.FullName, targetType.Name + “.cs”));
CodeGeneratorOptions codeGeneratorOptions = new CodeGeneratorOptions();
csharpProvider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, codeGeneratorOptions);
writer.Close();
}

// build the parameters for source compilation
CompilerParameters compilerParameter = new CompilerParameters();

// register referenced assemblies of the target type
RegisterReferences(targetType);

// add assembly references of all related assemblies (found in dictionary)
foreach (string value in _assemblyPaths.Values)
{
compilerParameter.ReferencedAssemblies.Add(value);
}

// register referenced assemblies of this type
string thispath = RegisterReferences(targetType);
compilerParameter.ReferencedAssemblies.Add(thispath);

// for the serialization
compilerParameter.ReferencedAssemblies.Add(“System.Xml.dll”);

// generate an in-memory class file instead of an executable
compilerParameter.GenerateExecutable = false;
compilerParameter.GenerateInMemory = true;

// invoke compilation
CompilerResults results = csharpProvider.CompileAssemblyFromDom(compilerParameter, codeCompileUnit);

// display compilation errors
foreach (CompilerError error in results.Errors)
{
Console.WriteLine(error.Line.ToString(CultureInfo.CurrentCulture) + “: ” + error.ErrorText);
}

if (results.Errors.HasErrors)
return null;
else
return results.CompiledAssembly;
}

I’ve made physical creation of class optional (done by the provider via its method GenerateCodeFromCompileUnit), so you can turn it on or off (for the sake of testing, it is good to see how the class looks like). Yo can see what is generated below (IClientClass.cs):

namespace MyCodeDom {
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

///
///——————————————————————————
///
/// This code was generated by a tool.
/// Runtime Version:2.0.50727.1433
///
/// Changes to this file may cause incorrect behavior and will be lost if
/// the code is regenerated.
///
///——————————————————————————
[System.Serializable()]
public class ClientClass : IClientClass {

private System.Guid _identifier;

private bool _active;

public virtual System.Guid Identifier {
get {
}
set {
}
}

public virtual bool Active {
get {
}
set {
}
}

public virtual void DoSomething() {
// put some code here.;
}
}
}

The registration of assemblies is important because you should be aware that CodeDOM doesn’t know anything about the number and location of used assemblies (i.e. namespaces), so I consider the CodeDOM, in that case, rather a container of code definition than a real mechanism. For this purpose, I’ve used a dictionary to put the assembly paths found on a way, and at last, I have just put the resulting paths (i.e. ReferencedAssemblies) inside the compiler parameter.

Of the parameters, only GenerateExecutable and GenerateInMemory is vital, since they determine that the stuff being generated will be in-memory class. After that, the class is compiled and any errors found are enumerated (and possibly handled by you afterwards). The compiler returns compiled assembly if successful or nothing if it fails.

I have to admit that I’ve rearranged my example to be more simple. In fact, I’ve removed some parts which I consider not to be important like: consideration about generic base type, handling code – the “meat” part and linking between class members. Also, handling of multiple generated classes I omit, but it could be easily expanded by creating some helper classes.

Kind regards.duck2

Advertisements

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

Categories

%d bloggers like this: