Label Cloud

Monday, June 04, 2007

Using CodeDOM to generate parsing classes

I needed to create a simple dynamic parser to create a class based on an array of objects into a class. This would be used to read a data from a DatReader. My first thought was to use reflection but having a little bit of spare time, I decided to make a sample of doing this using CodeDOM.

My data class:

namespace CodeDomTest { public class DataClass { private int _FirstInt; private long _SecondInt; private string _FirstString; private string _SecondString; public int FirstInt { get { return _FirstInt; } set { _FirstInt = value; } } public long SecondInt { get { return _SecondInt; } set { _SecondInt = value; } } public string FirstString { get { return _FirstString; } set { _FirstString = value; } } public string SecondString { get { return _SecondString; } set { _SecondString = value; } } } }

And the Interface that the parser would have to implement:

 

namespace CodeDomTest { public interface IParser { DataClass Parse(object[] s); } }

I started working backwards, and wrote a simple parser myself:

class StringParser : IParser { public DataClass Parse(object[] s) { DataClass _data = new DataClass(); _data.FirstInt = System.Convert.ToInt32(s[0]); _data.FirstString = System.Convert.ToString(s[1]); _data.SecondInt = System.Convert.ToInt32(s[2]); _data.SecondString = System.Convert.ToString(s[3]); return _data; } }

Now, all I needed is for the CodeDom to generate the above code, and I am done. Easy...

 

using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using Microsoft.CSharp; namespace CodeDomTest { class DataParserGenerator { private static Dictionary<Type, IParser> ParserCache = new Dictionary<Type, IParser>(); private static object SyncLock = new object(); public static IParser GetCachedParser(Type ReturnType, string[] ParseMap) { lock (SyncLock) { if (ParserCache.ContainsKey(ReturnType)) return ParserCache[ReturnType]; } IParser obj = GetParser(ReturnType, ParseMap); lock (SyncLock) { if (ParserCache.ContainsKey(ReturnType)) return ParserCache[ReturnType]; ParserCache.Add(ReturnType, obj); return obj; } } public static IParser GetParser(Type ReturnType, string[] ParseMap) { CodeCompileUnit targetUnit; CodeTypeDeclaration targetClass; targetUnit = new CodeCompileUnit(); CodeNamespace samples = new CodeNamespace(typeof(DataParserGenerator).Namespace.ToString()); samples.Imports.Add(new CodeNamespaceImport("System")); targetClass = new CodeTypeDeclaration("Parser_Generated_" + ReturnType.Name); targetClass.IsClass = true; targetClass.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed; targetClass.BaseTypes.Add(new CodeTypeReference(typeof (IParser))); samples.Types.Add(targetClass); targetUnit.Namespaces.Add(samples); AddParseMethod(targetClass, ReturnType, ParseMap); IParser obj = (IParser) ReturnCompiledClass(targetUnit, samples.Name + "." + targetClass.Name); return obj; } private static void AddParseMethod(CodeTypeDeclaration targetClass, Type ReturnType, string[] ParseMap) { CodeMemberMethod method = new CodeMemberMethod(); method.Name = "Parse"; method.Attributes = MemberAttributes.Public | MemberAttributes.Final; method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object[]), "param")); method.ReturnType = new CodeTypeReference(ReturnType); method.Statements.Add( new CodeVariableDeclarationStatement(ReturnType, "_data", new CodeObjectCreateExpression(ReturnType))); for (int i = 0; i < ParseMap.Length; i++) { PropertyInfo prop; //Only support properties for now. prop = ReturnType.GetProperty(ParseMap[i]); if (prop == null) continue; CodeArrayIndexerExpression mapIndexer = new CodeArrayIndexerExpression( new CodeVariableReferenceExpression("param"), new CodePrimitiveExpression(i)); //If System.Convert has a conversion method, use it. Otherwise, just cast if (typeof(System.Convert).GetMethod("To" + prop.PropertyType.Name, new Type[] { typeof(object) }) != null) { CodeTypeReferenceExpression ctr = new CodeTypeReferenceExpression(typeof(System.Convert)); CodeMethodInvokeExpression cme = new CodeMethodInvokeExpression(); cme.Method = new CodeMethodReferenceExpression(ctr, "To" + ReturnType.GetProperty(ParseMap[i]).PropertyType.Name); cme.Parameters.Add(mapIndexer); method.Statements.Add(new CodeAssignStatement( new CodeVariableReferenceExpression("_data." + ParseMap[i]), cme )); } else { method.Statements.Add(new CodeAssignStatement( new CodeVariableReferenceExpression("_data." + ParseMap[i]), new CodeCastExpression(prop.PropertyType, mapIndexer))); } } method.Statements.Add(new CodeMethodReturnStatement(new CodeArgumentReferenceExpression("_data"))); targetClass.Members.Add(method); } private static object ReturnCompiledClass(CodeCompileUnit targetUnit, string targetClassName) { CSharpCodeProvider provider = new CSharpCodeProvider(); // Build the parameters for source compilation. CompilerParameters cp = new CompilerParameters(); // Generate a class library. cp.GenerateExecutable = false; // Do not save the assembly as a physical file. cp.GenerateInMemory = true; // Need to referenc current assembly because it containts definitions of IParse and DataClass cp.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location); #if DEBUG StringBuilder sb = new StringBuilder(); StringWriter sw = new StringWriter(sb); provider.GenerateCodeFromCompileUnit(targetUnit, sw, null); sw.Close(); System.Diagnostics.Debug.WriteLine(sb.ToString()); #endif CompilerResults cr = provider.CompileAssemblyFromDom(cp, targetUnit); if (cr.Errors.Count == 0) { return cr.CompiledAssembly.CreateInstance(targetClassName); } else return null; } } }

Here's a sample way of calling the parser:

IParser dcParser; string[] s ={"1", "2", "3", "4"}; string[] ParseMap = new string[] { "FirstInt", "FirstString", "SecondInt", "SecondString" }; dcParser= DataParserGenerator.GetParser(typeof (DataClass), ParseMap); DataClass dc = dcParser.Parse(s);

And Here's the generated code (from the debug output window)

namespace CodeDomTest { using System; public sealed class Parser_Generated_DataClass : CodeDomTest.IParser { public CodeDomTest.DataClass Parse(object[] param) { CodeDomTest.DataClass _data = new CodeDomTest.DataClass(); _data.FirstInt = System.Convert.ToInt32(param[0]); _data.FirstString = System.Convert.ToString(param[1]); _data.SecondInt = System.Convert.ToInt64(param[2]); _data.SecondString = System.Convert.ToString(param[3]); return _data; } } }

That's all... I told you it's easy.


Share/Save/Bookmark

No comments: