Today I decided to create a run-time compiled Csv generator and compare it to a compile-time version of the same code. It works a treat with little noticeable difference in performance over 10000 runs (of a very small table)
These are the compile time definitions:
public class Client
{
public long id { get; set; }
public string forenames { get; internal set; }
public string surname { get; set; }
public long Office_id { get; set; }
}
public interface IExport
{
void Export(IDbConnection openConnection);
}
public class Exporter : IExport
{
public void Export(IDbConnection openConnection)
{
var results = openConnection
.Query<Client>("SELECT * FROM Client");
foreach (Client client in results)
{
string line =
client.id.ToString() + ',' +
client.forenames.ToString() + ',' +
client.surname.ToString() + ',' +
client.Office_id.ToString();
}
}
}
This is the string constant duplicate of the same code:
public const string ClassText = @"
public class Exporter : IExport
{
public void Export(IDbConnection openConnection)
{
var results = openConnection
.Query<Client>(""SELECT * FROM Client"");
foreach (Client client in results)
{
string line =
client.id.ToString() + ',' +
client.forenames.ToString() + ',' +
client.surname.ToString() + ',' +
client.Office_id.ToString();
}
}
public class Client
{
public long id { get; set; }
public string forenames { get; internal set; }
public string surname { get; set; }
public long Office_id { get; set; }
}
}";
This is the code that compiles the string into a type:
public static Type ClassFromString(string classStr, string className)
{
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("dapper.dll");
cp.ReferencedAssemblies.Add("system.dll");
cp.ReferencedAssemblies.Add("system.data.dll");
cp.ReferencedAssemblies.Add(
typeof(IExport).Assembly.GetModules()[0].FullyQualifiedName);
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
cp.IncludeDebugInformation = false;
StringBuilder code = new StringBuilder();
code.Append("using Dapper; using System; using System.Data; " +
"using System.Data.SqlClient; using CsvOut; ");
code.Append(classStr);
CompilerResults cr;
CodeDomProvider prov = null;
prov = CodeDomProvider.CreateProvider("cs");
cr = prov.CompileAssemblyFromSource(cp, code.ToString());
if (cr.Errors.HasErrors)
{
var e = new InvalidOperationException("Error Compiling Expression");
foreach (CompilerError err in cr.Errors)
{
e = new InvalidOperationException(
string.Format("Line {0}: {1}n", err.Line, err.ErrorText),
e);
}
throw e;
}
return cr.CompiledAssembly.GetType(className, true, true);
}
And this was the final version of the timing code:
class Program
{
static void Main(string[] args)
{
IExport compiletime = Activator
.CreateInstance(Type.GetType("CsvOut.Exporter")) as IExport;
IExport runtime = Activator
.CreateInstance(ClassFromString(ClassText, "Exporter")) as IExport;
CallIt(compiletime, 10);
CallIt(runtime, 10);
Console.Write(CallIt(compiletime, 10000));
Console.Write(" ");
Console.Write(CallIt(runtime, 10000));
Console.ReadKey();
}
static long CallIt(IExport instance, int count)
{
IDbConnection connection;
connection = new SqlConnection("...");
connection.Open();
Stopwatch timer = new Stopwatch();
timer.Start();
for (int i = 0; i < count; i++)
{
instance.Export(connection);
}
timer.Stop();
return timer.ElapsedMilliseconds;
}
}