Label Cloud

Thursday, June 14, 2007

Alternatives to Enum(s)

I had the need to provide an enumerated value to in my code, and be able to easily convert the value to a string representation. .NET Enum can only be numeric and did not provide the functionality needed. A coworker showed me very nice way to create the same functionality with a simple class

//Using this instead of an ENum - beeing fancy internal sealed class ActionType { private readonly string _action; private ActionType(string action) { this._action = action; } public override string ToString() { return _action; } public static readonly ActionType Update = new ActionType("U"); public static readonly ActionType Delete = new ActionType("D"); }

What the class allows me to do is the following:

main() { ActionType action = ActionType.Update; action = ActionType.Delete; string sAction = action.ToString(); }

sAction will have "D" as the string representation for the Delete action.


Share/Save/Bookmark

Friday, June 08, 2007

.CHM help files are not working

Sometimes I download help files from the Internet, and on some machines, they would not display. I'd receive an IE error "The page cannot be displayed".

I finally decided to look into it. The solution is explained in the KB902225

The specific thing that worked for me was:

  1. Right-click the CHM file, and then click Properties.
  2. Click Unblock.
  3. Double-click the .chm file to open the file.


Share/Save/Bookmark

Thursday, June 07, 2007

Making your assemblies describe themselves

One of the biggest hurdles of the release process is to make sure you know exactly what you are releasing. To make that job a little easier, I've modified my project files to generate and include build related information in the assembly properties.

I am using a great open source project MSBuild Community Tasks.

Three of the tasks included are Time, Version and AssemblyInfo. Here's the process to incorporate them into the project.

Add a new project to the visual studio solution. I made this a C# project to make it easier to integrate with Visual Studio. Then open the project in your favorite text editor. Scroll down to the <Target Name="Build"> line. Now make the contents of the target the following

<Time Format="yyyy/MM/dd HH:mm:ss">
<Output TaskParameter="FormattedTime" PropertyName="buildDate" />
</Time>
<AssemblyInfo CodeLanguage="CS" OutputFile="GlobalInfo.cs"
AssemblyDescription="Build Date: $(buildDate)
Configuration: $(Configuration)$(Platform)" />
<Version VersionFile="version.txt">
<Output TaskParameter="Major" PropertyName="Major" />
<Output TaskParameter="Minor" PropertyName="Minor" />
<Output TaskParameter="Build" PropertyName="Build" />
<Output TaskParameter="Revision" PropertyName="Revision" />
</Version>
<AssemblyInfo CodeLanguage="CS" OutputFile="AssemblyVersion.cs" AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />

Now add a new text file to the project called version.txt and edit to have a single line "1.0.0.0"

That is almost it. After compiling the above project, you will receive two new files: GlobalInfo.cs and AssemblyVersion.cs. GlobalInfo.cs will contain the BuildDate and Configuration used during compilation. AssemblyVersion.cs will contain the version information based on the version.txt file. See help for AssemblyVersion task for how to make it increment the version number during the build.

Another task is to add the two new files as a replacement to the AssemblyInfo.cs that's usually a part of every solution. I do it with a text editor to make sure that they point to the file outside of the local project but rather to the newly generated files. That makes files read-only. The last task is to make sure that build dependency is properly set and the "GenerateVersion" project will be built first.

What you achieve after doing all of the above is that all compiled assemblies (.DLL and .EXE) will have a shared version number across multiple assemblies. They will also have in their properties tab information on when they where built and the configuration used during built. That can be used to troubleshoot and to easy production deployments.


Share/Save/Bookmark

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