I do most of my work in C++ and have a bit of an abusive relationship with my compiler. It yells long error messages at me at the slightest provocation and I do my best to placate it. In return I gain a sense of security that “If it compiles it’ll (probably) work”[1].
I used to have the same sort of relationship with my C# compiler (only with less error messages) but lately I’m beginning to have some trust issues.
It all started one day when I was refactoring some code to use regular expressions (the types have been changed to protect the guilty).
string pattern = "needle";
/* Snip N lines */
System.Console.WriteLine(haystack.Contains(pattern));
This code needlessly matches “needlessly”, well that’s simple enough to solve, I’ll just replace pattern with a regular expression including word boundaries:
Regex pattern = new Regex("\bneedle\b");
/* Snip N lines */
System.Console.WriteLine(pattern.IsMatch(haystack));
It compiles, ship it!
After all the buildup it should not be awfully surprising that the code in question is not shipping quality. At runtime it throws an exception saying it can’t cast a string to a Regex.
When expanding the N lines in the above code I see this:
if (someCondition)
{
dynamic obj = GetDynamicObject();
pattern = string.Format("{0}:{1}", obj.value, pattern);
}
Why the hell does this compile[2]? It’s obvious that String.Format always returns string which is incompatible with Regex!
Well actually it isn’t, String.Format doesn’t always return string. dynamic is contagious, any function that is called with at least one dynamic argument will return dynamic, thus dramatically lowering the value the compiler has as an error detector.
In fact if you’re in the habit of using var whenever possible you may be helping to spread this contagion. Previously we thought that writing type names is redundant and the following lines are equivalent:
string s1 = Path.Combine(directory, file);
var s2 = Path.Combine(directory, file);
Some would even prefer the second line saying it’s redundant to tell the compiler something it already knows, but if one of the variables you send to this (or any other) function is of type dynamic it will infect s2 with its type and so on transitively.
[1] This is hyperbole of course, what I mean is that I expect the compiler to catch all type errors. ↩
[2] In real life it was worse, the string.Format had several arguments only one of which was dynamic and this declared many lines above the offending line. ↩