Frans Bouma recently posted a question to DevelopMentor's Advanced .NET mailing list. He has a requirement to generate a type string that is valid in VB.NET.
Of course, since the Type
class is a CLRism, it is language-agnostic, so it
returns strings that look how it wants them to. For simple types, these are just type names,
and so translated into any language that presents .NET types more or less directly. (E.g.
VB.NET or C#.) But for arrays, things are a little different. VB.NET isn't happy with its
representation because square brackets are used: the type object for an array of integers
reports its name as System.Int32[]
. (C# happens to be happy with this, but there
are other examples that aren't valid C# either, e.g. a one-dimensional integer array with a non-zero
lower bound will count out as System.Int32[*]
.)
To get around this, I suggested using the CodeDom. Frans had the perfectly reasonable reaction that the CodeDom was way too complex, which, to be fair, it is. It's fairly hard work generating code this way. He didn't want to have to rewrite his code around CodeDom, which is a perfectly sane point of view.
I was about to reply saying that I agreed when my inability to resist a challenge took
over. I thought to myself that surely it must be possible to write a wrapper class that
tackled the CodeDom to do the necessary work, encapsulating all the complexity. This class
could just present a very simple API that let you pass in a Type
object, and
it would return the appropriate string.
It turned out to be a whole lot simpler than I though it would be. Here it is:
using System; using System.IO; using System.CodeDom.Compiler; using System.CodeDom; using Microsoft.VisualBasic; public class CodeUtils { public static string GenVBArrayDeclaration(Type arrayType, string varName) { if (arrayType == null) throw new ArgumentNullException("arrayType"); if (varName == null) throw new ArgumentNullException("varName"); if (!arrayType.IsArray) throw new ArgumentException("Type must be an array", "arrayType"); VBCodeProvider cp = new VBCodeProvider(); ICodeGenerator cg = cp.CreateGenerator(); CodeVariableDeclarationStatement stmt = new CodeVariableDeclarationStatement(arrayType, varName); using (StringWriter output = new StringWriter()) { cg.GenerateCodeFromStatement(stmt, output, null); output.Flush(); return output.ToString(); } } }
If you pass this the type of an array of integers, and the name myVar
it will produce this output:
Dim myVar() As Integer
It surprised me how easy it was to use the CodeDom here, mostly because my
previous experience with the CodeDom has involved taking the rather more
all-encompasing approach of using ICodeCompiler
. I hadn't used
ICodeGenerator
before, or maybe I had but didn't notice that it
could be used to generate fragments like this. Of course it makes perfect
sense that it can - it has to be able to in order to allow designers in
Visual Studio .NET that use it to be able to insert generated fragments of
code into existing source files. But given how much effort it is to build
the entire source file from scratch, I was impressed at how easy it was
to generate a snippet.