watkipet watkipet - 9 days ago 5
C# Question

How to handle printed curly braces in early evaluation of String.Format in a custom WriteLine in C#

I'm writing a C# code generator in C#. However, I'm running into a problem printing initializer lists. I've dumbed down my code generator to a contrived example. Suppose I'd like to output the following class:

class Foo
{
private static int[] m_bar = new[] {256};
}


Here's an example program to do that:

class Program
{
private static int m_indentLevel;

static void Main(string[] args)
{
var arrayDimension = 256;

WriteLine("class Foo");
WriteLine("{{");
m_indentLevel++;

// This works because the format string is passed all the way through to the
// Console.WriteLine
string initializer = String.Format("new[] {{{0}}}", arrayDimension);
WriteLine("private static int[] m_bar = {0};", initializer);

// This doesn't work because the format string is evaluated before the Console.WriteLine.
var intirim = String.Format("private static int[] m_bar = {0};", initializer);
WriteLine(intirim);

m_indentLevel--;
WriteLine("}}");
}

private static void WriteLine(String format, params Object[] arg)
{
Console.WriteLine("{0}{1}", new String(' ', m_indentLevel * 2), String.Format(format, arg));
}
}


This program keeps track of indentation in the output and uses a custom
WriteLine
method to indent the given string to the proper level. My problem comes when the user of my custom
WriteLine
uses curly braces (escaped) in the given format string and calls my
WriteLine
without specifying the optional
arg
, i.e. they evaluate the format string before passing it to me.

Notice that the first attempt to write
private static int[] m_bar = new {256};
succeeds while the second throws an exception.

Is it possible to make my
WriteLine
work in both situations? How do I do that?

Answer

If you want your WriteLine() to have two different semantics for the format parameter depending on whether or not there are any additional parameters following it, my own preference would be to write an overload for that case:

/// <summary>
/// Indent s and write to Console.Out without String.Format() interpolation
/// </summary>
public static void WriteLine(string s) { ... }

This follows Console.WriteLine() and String.Format() practice; both have quite a number of overloads. That's necessary in part because overload resolution with params and object is tricky, fragile stuff. Be constantly on the alert out for the wrong overload getting called.

That said, your design scares me. It reminds me of "quick and easy" designs I've come up with over the years, where I thought I could use some language feature to do most of the work for me, and I rapidly found myself thrashing around in quicksand. You get a bunch of cool stuff that you want for free -- but you get a lot of other stuff besides that doesn't suit your needs at all well.

It's like you're using an octopus to lash a canoe to your car. Certainly, it has all those nice long grippy tentacles, but it has its own agenda. In the long run, rope may turn out to require less effort and ingenuity on your part, when all is said and done.