100% Pure

accept no imitations
Everything here is my opinion. I do not speak for your employer.
September 2007
October 2007

2007-09-01 »

Disturbing adventures in preprocessing, continued

I've been getting quite some interest in my earlier article, In praise of the C preprocessor. First, pcolijn wrote to point out that Java can delay evaluation of expensive toString() operations until later, so the cost of an unprinted log message can be pretty unimportant.

Then, fellow alumnit slajoie (I forgot how awesome his website was) wrote me to say that actually, the exact side-effect-reducing behaviour possible in C/C++ via the preprocessor was available in .Net due to a highly specific language feature:

    using System;
    using System.Diagnostics;

    class Test { public int i = 1000;

    [Conditional("FOO")]
    private void MaybeAdd(int incr)
    {
    i += incr;
    }
    
    private int SideEffect()
    {
    return ++i;
    }
    
    public static int Main(string[] args)
    {
    Test o = new Test();
    Console.WriteLine("{0}", o.i);
    o.SideEffect();
    Console.WriteLine("{0}", o.i);
    o.MaybeAdd(o.SideEffect());
    Console.WriteLine("{0}", o.i);
    return 0;
    }
    

    }

Compile this with "csc foo.cs" (or use mono's gmcs) and run it, and you get "1000 1001 1001". But use "csc /d:FOO foo.cs" (or use gmcs) and you get "1000 1001 2004".

The magic "[Conditional]" attribute not only makes a function not get included if you leave out the define, but it makes the caller of the function not evaluate its parameters at all. They're still type-checked, however. And note the neat way in which you don't have to define both the normal and "empty" versions of MaybeAdd() - it's automatic.

This is used already by System.Diagnostics.Debug.Assert and the related Trace stuff, among other things.

The decision to implement a particular feature of the preprocessor via a specific language feature (instead of simply providing a generic solution like cpp) is an interesting one that C#/.Net takes in several other places too. For example, C# generics aren't nearly as powerful as C++ templates, but because they're tuned for a specific purpose (and #@%! function pointers aren't insane in C#, so you don't need them for that) they actually work much better than templates in C++.

UPDATE: Oh dear, the C# situation is even crazier than I thought. Apparently conditional attributes weren't enough for them. Now we have partial methods in which functions that are declared, but not defined, can be called without a problem but their parameters aren't even evaluated. I don't understand the explanation ("lightweight callbacks") for why this is useful, but I assume it must be, because it seems like way too much work to invent otherwise.

I'm CEO at Tailscale, where we make network problems disappear.

Why would you follow me on twitter? Use RSS.

apenwarr on gmail.com